Foundation — i18n keys¶
Scope note: After the M0 cleanup ticket, foundation owns only two always-loaded namespaces: common and validation. The identity namespace moves to lazy-loaded — it has no content today (the vibe-coded login is being deleted) and will be re-introduced (along with its always-loaded designation, if needed) by identity's first ticket. Per-module namespaces (item-catalog, manufacturing, …) are created by each module's first ticket and live outside this plan.
The validation namespace is never written manually — zod-i18n-map provides the keys. The list below is the structural reference for what should exist; if anything's missing from the actual JSON files after foundation tickets land, that's a bug fix in the M0 cleanup ticket.
Locale files: src/locales/en/{common,validation}.json and src/locales/pl/{common,validation}.json. The identity.json files are emptied (or deleted) by the M0 cleanup ticket — left empty {} if their existence is convenient for tooling, deleted otherwise.
Namespace: common¶
Loaded always. Used by every page.
nav.* — sidebar + breadcrumb labels¶
| Key | en | pl |
|---|---|---|
nav.dashboard |
Dashboard | Pulpit |
Module-grouping section headers (nav.group.identity, nav.group.itemCatalog, etc.) are added by each module's first ticket — foundation seeds none of them since no module has shipped yet.
actions.* — semantic button labels¶
Owned by SaveButton, CancelButton, DeleteButton, etc. Call sites never pass these strings.
| Key | en | pl |
|---|---|---|
actions.save |
Save | Zapisz |
actions.cancel |
Cancel | Anuluj |
actions.delete |
Delete | Usuń |
actions.confirm |
Confirm | Potwierdź |
actions.edit |
Edit | Edytuj |
actions.add |
Add | Dodaj |
actions.apply |
Apply | Zastosuj |
actions.reset |
Reset | Resetuj |
actions.refresh |
Refresh | Odśwież |
actions.backToDashboard |
Back to dashboard | Wróć do panelu |
state.* — page/data states¶
| Key | en | pl |
|---|---|---|
state.loading |
Loading… | Ładowanie… |
state.empty |
No data to display | Brak danych |
state.refreshing |
Refreshing… | Odświeżanie… |
state.saving |
Saving… | Zapisywanie… |
errors.* — error messages used app-wide¶
Two key styles coexist here. Codes emitted by the BE (grinsystem-api/src/shared/exceptions.py) use the literal BE code as the i18n key — UPPER_SNAKE — so the lookup is a mechanical concat (common:errors.${err.code}) with no transform layer that could drift. Transport / general / page-chrome strings use the lowerCamelCase convention everywhere else in this doc. This is the one explicit exception to convention §1 below.
The full keyset is landed by GRF-9 (M3 error envelope foundation).
BE-code keys (one per shared exception class)¶
| Key | en | pl |
|---|---|---|
errors.VALIDATION_ERROR |
Some of the entered data is invalid. | Niektóre dane są nieprawidłowe. |
errors.CONCURRENT_MODIFICATION |
This record was changed by someone else. Refresh to see the latest version. | Ten rekord został zmieniony przez kogoś innego. Odśwież, aby zobaczyć aktualną wersję. |
errors.FORBIDDEN_INSUFFICIENT_PERMISSION |
You don't have permission for this action. | Brak uprawnień do tej akcji. |
errors.FORBIDDEN |
Access denied. | Brak dostępu. |
errors.UNAUTHORIZED |
Your session has expired. Sign in again. | Sesja wygasła. Zaloguj się ponownie. |
errors.NOT_FOUND |
The requested resource was not found. | Nie znaleziono żądanego zasobu. |
errors.CONFLICT |
Operation conflicts with the current state. | Operacja jest sprzeczna z aktualnym stanem. |
errors.IDEMPOTENCY_KEY_REQUIRED |
Internal error: missing idempotency key. Try again. | Błąd wewnętrzny: brak klucza idempotentności. Spróbuj ponownie. |
errors.IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD |
This action was already submitted with a different payload. | Ta akcja została już wysłana z innymi danymi. |
errors.IMMUTABLE_FIELD_CANNOT_BE_CHANGED |
Internal error: tried to change a read-only field. | Błąd wewnętrzny: próba zmiany pola tylko do odczytu. |
errors.INVALID_SORT_FIELD |
Invalid sort option. Resetting to default. | Nieprawidłowa opcja sortowania. Przywrócono domyślną. |
errors.REPOSITORY_ERROR |
Server error. Try again later. | Błąd serwera. Spróbuj ponownie później. |
errors.DOMAIN_ERROR |
Operation could not be completed. | Nie udało się wykonać operacji. |
Transport / page-chrome keys (lowerCamelCase)¶
| Key | en | pl |
|---|---|---|
errors.networkError |
Could not reach the server. Check your connection and try again. | Nie udało się połączyć z serwerem. Sprawdź połączenie i spróbuj ponownie. |
errors.serverError |
Something went wrong. Try again later. | Coś poszło nie tak. Spróbuj ponownie później. |
errors.notFound |
Not found | Nie znaleziono |
errors.unexpected |
An unexpected error occurred. Please try again. | Wystąpił nieoczekiwany błąd. Spróbuj ponownie. |
errors.insufficientPermission |
You don't have permission for this action. | Brak uprawnień do tej akcji. |
errors.forbiddenTitle |
Access denied | Brak dostępu |
errors.forbiddenBody |
Contact your administrator if you believe you should have access to this page. | Skontaktuj się z administratorem, jeśli uważasz, że ta strona powinna być dostępna. |
errors.insufficientPermissionis the i18n key referenced by the Permission-Gated UI convention — see../../standards.md§ Permission-Gated UI. Per-module<module>:errors.<CODE>entries are added opportunistically by each feature module's first form that can trigger them — seedata-fetching.md§ Errors.
confirm.* — generic confirmation dialog text¶
| Key | en | pl |
|---|---|---|
confirm.deleteTitle |
Delete this item? | Usunąć ten element? |
confirm.deleteBody |
This action cannot be undone. | Tej akcji nie można cofnąć. |
confirm.discardChangesTitle |
Discard changes? | Odrzucić zmiany? |
confirm.discardChangesBody |
Your unsaved changes will be lost. | Niezapisane zmiany zostaną utracone. |
app.* — app-level branding¶
| Key | en | pl |
|---|---|---|
app.name |
GrinSystem | GrinSystem |
User-menu items (
app.userMenu.account,app.userMenu.signOut) land with identity's first ticket — they have nothing to point to until then.
Namespace: identity¶
Empty in foundation. The pre-existing identity.json files (with vibe-coded login keys) are emptied or deleted by the M0 cleanup ticket. Identity's first ticket re-seeds this namespace with nav.login, login.*, account.*, permission.*, etc. — and decides at that point whether it warrants always-loaded status (probably yes, since the login screen has to render before any auth).
Namespace: validation¶
Never authored by hand. This namespace is supplied by zod-i18n-map. The package ships translations for every built-in Zod issue type; we only override an entry if the default Polish is awkward in a domain context.
Current overrides: none. If a module needs a custom message (e.g. "SKU must match pattern XYZ"), the message is supplied at schema definition time via .message('keyName') and resolved against the module's own namespace, not validation. Keep validation strictly for Zod's built-ins.
Conventions¶
- Keys are dot-notation paths, lowerCamelCase segments. Never kebab-case in keys. Exception:
errors.<BE_CODE>entries use the literal UPPER_SNAKE BE exception code as the segment so thecommon:errors.${err.code}lookup is mechanical — see §errors.*above. - One JSON file per namespace per locale —
src/locales/{en,pl}/{namespace}.json. No nesting JSON files inside subdirectories. - Status enums translate by value — example:
t(\itemType.${item.item_type}`). The mapping lives in the module namespace, not incommon`. - Polish is the primary locale. Write the Polish copy first when drafting new keys; the English is a translation, not the source.
- Semantic buttons own their labels. Call sites never pass text.
<SaveButton />resolvescommon:actions.saveinternally — passing alabelprop is forbidden. - Placeholders use
{{name}}syntax per i18next convention. Example:t('itemCount', { count: 5 })→"5 items"/"5 elementów"(with_one/_otherplural variants in the JSON).