Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
23989b1
feat(dashboard): localize the tenant app with react-i18next
marcelo-maciel Aug 17, 2026
e8c0693
build(deps): bump Testcontainers to 4.14.0 and SourceLink past their …
marcelo-maciel Sep 14, 2026
b13fb9f
fix(infra): pull MinIO from quay.io on a pinned tag, not Docker Hub
marcelo-maciel Sep 14, 2026
eb0d38d
fix(dashboard): pass the unit token to the count chip, not a translat…
marcelo-maciel Sep 17, 2026
193dc83
fix(dashboard): keep the session when a speculative refresh fails
marcelo-maciel Sep 18, 2026
f24834d
test(dashboard): gate interpolation parity, not just keys
marcelo-maciel Sep 18, 2026
82b4afb
feat(dashboard): localize the command palette search keywords
marcelo-maciel Sep 18, 2026
e979f1e
fix(dashboard): tell the user when the language could not be saved
marcelo-maciel Sep 18, 2026
d3d4474
test(dashboard): cover the failed-save toast
marcelo-maciel Sep 18, 2026
012ecca
fix(dashboard): stop a raw catalog key from reaching the screen
marcelo-maciel Sep 18, 2026
86f488b
fix(infra): pull minio/mc from quay.io too, not just minio/minio
marcelo-maciel Sep 18, 2026
0f5e6b7
build(deps): drop the dead SSH.NET pin
marcelo-maciel Sep 18, 2026
57586b7
fix(i18n): stop the missing-key handler from eating every defaultValue
marcelo-maciel Sep 18, 2026
0fd8b3f
fix(dashboard): format counts as numbers, and localize upload failures
marcelo-maciel Sep 18, 2026
d966da7
fix(dashboard): let a lost upload key fall through to the segment fal…
marcelo-maciel Sep 18, 2026
4f5a99c
fix(i18n): namespace the language key and localize the last upload path
marcelo-maciel Sep 18, 2026
70664b1
test(i18n): pin the phone number the language switch echoes back
marcelo-maciel Sep 18, 2026
f7d2708
Merge origin/main into feat/i18n-dashboard
marcelo-maciel Sep 25, 2026
d46c10f
fix(dashboard): keep a clean profile form on the current ETag across …
marcelo-maciel Sep 25, 2026
dd2d763
Merge origin/main into feat/i18n-dashboard
marcelo-maciel Sep 25, 2026
49b7241
fix(dashboard): format the file preview's created date in the app lan…
marcelo-maciel Sep 25, 2026
a918787
Merge branch 'main' into feat/i18n-dashboard
marcelo-maciel Sep 26, 2026
cabe52b
Merge remote-tracking branch 'origin/main' into feat/i18n-dashboard
marcelo-maciel Sep 28, 2026
44ff666
Merge remote-tracking branch 'origin/main' into feat/i18n-dashboard
marcelo-maciel Oct 2, 2026
707a031
test(dashboard): allow a missing pt-BR key and fall back to English a…
marcelo-maciel Oct 2, 2026
a6260ea
perf(dashboard): load non-default language catalogs on demand
marcelo-maciel Oct 2, 2026
1eaa748
fix(dashboard): report the language on screen when a catalog fails to…
marcelo-maciel Oct 2, 2026
a54969b
Merge remote-tracking branch 'origin/main' into feat/i18n-dashboard
marcelo-maciel Oct 15, 2026
74d8a0d
chore(dashboard): fail the lint on untranslated JSX text
marcelo-maciel Oct 15, 2026
db7f650
feat(dashboard): type every t() key against the en-US catalogs
marcelo-maciel Oct 15, 2026
6b1fecf
docs(agents): document the frontend t() / namespace pattern
marcelo-maciel Oct 15, 2026
f77c30e
fix(dashboard): check the status maps against the backend enums
marcelo-maciel Oct 15, 2026
fdea5c0
docs(agents): second translators and the status-key maps in the front…
marcelo-maciel Oct 15, 2026
39cc11e
Merge remote-tracking branch 'origin/main' into pr-1384
iammukeshm Nov 3, 2026
ca38cde
Merge remote-tracking branch 'origin/main' into pr-1384
iammukeshm Nov 3, 2026
ae6c4e4
Merge remote-tracking branch 'origin/main' into pr-1384
iammukeshm Nov 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions clients/dashboard/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import i18next from 'eslint-plugin-i18next';
// The plugin exports no defaults object; this is 6.x's internal path, and an upgrade that moves it
// fails the lint at import rather than silently dropping the defaults.
import i18nextDefaults from 'eslint-plugin-i18next/lib/options/defaults.js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
Expand Down Expand Up @@ -48,4 +52,31 @@ export default tseslint.config(
'jsx-a11y/anchor-has-content': 'error',
},
},
{
// Text rendered in JSX goes through t(): an unwrapped literal would ship in English to every
// language. Scoped to where the UI lives; tests sit outside src/ and are not linted for this.
files: ['src/pages/**/*.tsx', 'src/components/**/*.tsx'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
'error',
{
mode: 'jsx-text-only',
// Code identifiers and key caps are not prose.
'jsx-components': { exclude: [...i18nextDefaults['jsx-components'].exclude, 'code', 'kbd'] },
// The plugin replaces, not merges, a list it is given, so its defaults are spread back in.
words: {
exclude: [
...i18nextDefaults.words.exclude,
// Separators, units and glyphs (·, %, /, ✓) carry no words to translate.
/^[\p{P}\p{S}\p{N}\s]+$/u,
// The product name and the build tag stay as they are in every language.
/^(fullstack|hero|fullstackhero)$/,
/^v\d+(\.\d+)*( · dashboard)?$/,
],
},
},
],
},
},
);
129 changes: 128 additions & 1 deletion clients/dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions clients/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.475.0",
"qrcode": "^1.5.4",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-i18next": "^17.0.10",
"react-router-dom": "^7.18.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0"
Expand All @@ -39,6 +42,7 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.7.0",
"eslint": "^9.39.4",
"eslint-plugin-i18next": "^6.1.5",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26",
Expand Down
7 changes: 7 additions & 0 deletions clients/dashboard/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export default defineConfig({
navigationTimeout: 15_000,
},

// Assertions get the same budget as actions. Left at the 5s default they were
// the tightest deadline in the suite — every test ends in a toBeVisible, and
// under CPU contention (a second suite running, a loaded dev server) the first
// paint of a lazy route lands past 5s while staying well inside the action and
// navigation budgets. That asymmetry, not the specs, is what made runs flaky.
expect: { timeout: 10_000 },

projects: [
{
name: "chromium",
Expand Down
3 changes: 2 additions & 1 deletion clients/dashboard/public/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"defaultTenant": "root",
"demoMode": true,
"inactivityIdleMs": 1200000,
"inactivityWarningMs": 60000
"inactivityWarningMs": 60000,
"defaultLanguage": "en-US"
}
7 changes: 7 additions & 0 deletions clients/dashboard/src/api/billing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { ParseKeys } from "i18next";
import { apiFetch, ApiRequestError } from "@/lib/api-client";
import { INVOICE_STATUS_KEYS, statusKey } from "@/lib/status-keys";
import { env } from "@/env";
import { tokenStore } from "@/auth/token-store";

Expand All @@ -23,6 +25,11 @@ export type UsageSnapshotDto = {

export type InvoiceStatus = "Draft" | "Issued" | "Paid" | "Void" | (string & {});

/** The `subscription` catalog key for a status, or undefined for one the server added after this build. */
export function invoiceStatusKey(status: InvoiceStatus): ParseKeys<"subscription"> | undefined {
return statusKey(INVOICE_STATUS_KEYS, status);
}

export type InvoicePurpose = "Subscription" | "Usage" | (string & {});

export type InvoiceLineItemKind = "BaseFee" | "Overage" | "Adjustment" | (string & {});
Expand Down
4 changes: 4 additions & 0 deletions clients/dashboard/src/api/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type UserDto = {
phoneNumber?: string;
imageUrl?: string;
twoFactorEnabled?: boolean;
locale?: string | null;
};

export type UserRoleDto = {
Expand Down Expand Up @@ -398,6 +399,8 @@ export type UpdateProfileInput = {
firstName: string | null;
lastName: string | null;
phoneNumber: string | null;
/** BCP 47 UI language tag persisted on the user (drives the JWT locale claim). */
locale: string | null;
};

/**
Expand Down Expand Up @@ -447,6 +450,7 @@ export async function updateMyProfile(input: UpdateProfileInput): Promise<void>
firstName: input.firstName ?? profile.firstName ?? null,
lastName: input.lastName ?? profile.lastName ?? null,
phoneNumber: input.phoneNumber ?? profile.phoneNumber ?? null,
locale: input.locale ?? profile.locale ?? null,
deleteCurrentImage: false,
}),
});
Expand Down
7 changes: 7 additions & 0 deletions clients/dashboard/src/api/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { apiFetch } from "@/lib/api-client";
import type { ParseKeys } from "i18next";
import type { PagedResult } from "@/api/billing";
import { statusKey, TOPUP_STATUS_KEYS } from "@/lib/status-keys";

// Wallet transaction kinds. The backend serializes enums as STRINGS
// (see project_api_string_enums); mirror them as a string union with an
Expand Down Expand Up @@ -39,6 +41,11 @@ export type TopupRequestStatus =
| "Cancelled"
| (string & {});

/** The `subscription` catalog key for a status, or undefined for one the server added after this build. */
export function topupStatusKey(status: TopupRequestStatus): ParseKeys<"subscription"> | undefined {
return statusKey(TOPUP_STATUS_KEYS, status);
}

export type TopupRequestDto = {
id: string;
tenantId: string;
Expand Down
Loading
Loading