Item Catalog — i18n Keys¶
Proposed keys for src/locales/en/item-catalog.json and src/locales/pl/item-catalog.json. Polish translations are starting drafts — refine during implementation against actual UI screens. English is the development source.
Namespace: item-catalog (kebab-case, matches FE folder + URL stem). Loaded by i18next when the first catalog route is visited. Always-loaded common namespace owns the sidebar label (common:nav.catalog, common:nav.items, common:nav.groups) — those are added to the common locale files, not here, when M0 ticket 12 lands the NavItems.
Conventions¶
- Status enums translate by value —
t('item-catalog:itemType.${item.item_type}'). Keys mirror BE enum literals (lower snake_case foritemType, lower forstorageUnitandstatus). - Error messages from the BE error envelope are NOT translated here. Field-level errors come from the
validationnamespace via zod-i18n-map; route-level errors are surfaced as toasts whose copy is owned by the sharedhandleApiErrorparser. Only the four item-deactivation guard codes get module-local copy below because they're rendered as inline alerts inside the deactivateConfirmDialog(route-level toast doesn't fit the UX). - Semantic buttons own their own translation (
<SaveButton />renders "Zapisz" / "Save" fromcommon). Never pass button labels from page-level keys. - Pluralisation uses i18next's plural rules (
_one,_other, etc.). Polish uses three forms (_one,_few,_other) per CLDR — covered explicitly foritemCountand the import success count.
Proposed tree¶
{
"page": {
"itemList": {
"title": { "pl": "Katalog pozycji", "en": "Item catalog" },
"description": { "pl": "Wszystkie surowce, półprodukty, produkty i opakowania.", "en": "All ingredients, intermediates, products and packaging." },
"createButton": { "pl": "Nowa pozycja", "en": "New item" },
"importButton": { "pl": "Importuj", "en": "Import" },
"filter": {
"searchPlaceholder": { "pl": "Szukaj po nazwie, kodzie, opisie, tagach…", "en": "Search by name, code, description, tags…" },
"type": { "pl": "Typ", "en": "Type" },
"status": { "pl": "Status", "en": "Status" },
"group": { "pl": "Grupa", "en": "Group" },
"groupAll": { "pl": "Wszystkie grupy", "en": "All groups" },
"groupPickTypeFirst":{ "pl": "Najpierw wybierz typ", "en": "Pick a type first" },
"reset": { "pl": "Resetuj filtry", "en": "Reset filters" }
},
"stats": {
"total": { "pl": "Łącznie", "en": "Total" },
"active": { "pl": "Aktywne", "en": "Active" },
"archived": { "pl": "Zarchiwizowane", "en": "Archived" },
"byType": { "pl": "Według typu", "en": "By type" }
},
"column": {
"code": { "pl": "Kod", "en": "Code" },
"name": { "pl": "Nazwa", "en": "Name" },
"type": { "pl": "Typ", "en": "Type" },
"group": { "pl": "Grupa", "en": "Group" },
"unit": { "pl": "Jednostka", "en": "Unit" },
"status": { "pl": "Status", "en": "Status" }
},
"emptyAll": { "pl": "Brak pozycji w katalogu", "en": "No items in the catalog yet" },
"emptyAllCta": { "pl": "Dodaj pierwszą pozycję", "en": "Add the first item" },
"emptyFiltered": { "pl": "Brak pozycji pasujących do wybranych filtrów", "en": "No items match the current filters" },
"emptyFilteredCta": { "pl": "Wyczyść filtry", "en": "Clear filters" }
},
"itemDetail": {
"backToList": { "pl": "Wróć do katalogu", "en": "Back to catalog" },
"editButton": { "pl": "Edytuj", "en": "Edit" },
"deactivateButton": { "pl": "Dezaktywuj", "en": "Deactivate" },
"reactivateButton": { "pl": "Aktywuj ponownie", "en": "Reactivate" },
"systemBadge": { "pl": "Pozycja systemowa", "en": "System item" },
"section": {
"information": { "pl": "Informacje o pozycji", "en": "Item information" },
"inventory": { "pl": "Parametry magazynowe", "en": "Inventory parameters" },
"descriptionTags": { "pl": "Opis i tagi", "en": "Description & tags" },
"audit": { "pl": "Audyt", "en": "Audit" },
"activity": { "pl": "Aktywność", "en": "Activity" }
},
"audit": {
"createdBy": { "pl": "Utworzona przez", "en": "Created by" },
"createdAt": { "pl": "Utworzona", "en": "Created" },
"updatedBy": { "pl": "Zaktualizowana przez", "en": "Updated by" },
"updatedAt": { "pl": "Zaktualizowana", "en": "Updated" },
"never": { "pl": "Nigdy", "en": "Never" }
},
"activityPlaceholder": { "pl": "Pełna historia zdarzeń pojawi się gdy backend uruchomi dziennik audytu.", "en": "Full event history will appear once the backend audit log is wired up." },
"deactivateConfirmTitle": { "pl": "Dezaktywować pozycję?", "en": "Deactivate this item?" },
"deactivateConfirmBody": { "pl": "Pozycja zostanie ukryta w nowych formularzach. Kod pozostaje zarezerwowany i nie będzie można go użyć dla innej pozycji.", "en": "The item will be hidden from new forms. Its code stays reserved and cannot be reused by another item." },
"deactivateConfirmCta": { "pl": "Dezaktywuj", "en": "Deactivate" },
"reactivateConfirmTitle": { "pl": "Aktywować pozycję ponownie?", "en": "Reactivate this item?" },
"reactivateConfirmBody": { "pl": "Pozycja będzie ponownie dostępna w wyszukiwarce i formularzach.", "en": "The item will be available again in search and forms." },
"reactivateConfirmCta": { "pl": "Aktywuj", "en": "Reactivate" }
},
"itemCreate": {
"title": { "pl": "Nowa pozycja", "en": "New item" },
"description": { "pl": "Dodaj pozycję do katalogu.", "en": "Add an item to the catalog." },
"backToList": { "pl": "Wróć do katalogu", "en": "Back to catalog" },
"fieldset": {
"identity": { "pl": "Identyfikacja", "en": "Identity" },
"classification": { "pl": "Klasyfikacja", "en": "Classification" },
"inventory": { "pl": "Parametry magazynowe", "en": "Inventory parameters" },
"descriptionTags": { "pl": "Opis i tagi", "en": "Description & tags" }
},
"helperImmutable": { "pl": "Kod, typ i jednostkę nadaje się raz – nie można ich później zmienić.", "en": "Code, type and unit are set once — they cannot be changed later." }
},
"itemEdit": {
"title": { "pl": "Edytuj pozycję", "en": "Edit item" },
"description": { "pl": "Pola zablokowane to identyfikatory pozycji – ich zmiana zerwałaby spójność BOM-ów, stanów i faktur.", "en": "Locked fields are item identifiers — changing them would break BOM, stock and invoicing integrity." },
"lockedHint": { "pl": "Pola zablokowane", "en": "Locked fields" },
"lastUpdated": { "pl": "Ostatnia zmiana: {{datetime}} przez {{user}}", "en": "Last updated {{datetime}} by {{user}}" },
"conflict": {
"title": { "pl": "Pozycja zmieniła się w międzyczasie", "en": "This item changed in the meantime" },
"body": { "pl": "Inny użytkownik zapisał zmiany zanim wysłano formularz. Załaduj najnowszą wersję, aby kontynuować edycję.", "en": "Another user saved changes before this form was submitted. Reload the latest version to continue editing." },
"reloadCta": { "pl": "Załaduj najnowszą wersję", "en": "Reload latest" }
},
"partialSave": {
"groupSectionError": { "pl": "Nie udało się zapisać przypisania do grupy.", "en": "Could not save the group assignment." }
}
},
"itemImport": {
"title": { "pl": "Import pozycji z Excela", "en": "Import items from Excel" },
"description": { "pl": "Wgranie pliku wykonuje import w trybie wszystko-albo-nic. Każdy błąd w wierszu wstrzymuje zapis całej paczki.", "en": "Uploading the file runs an all-or-nothing import. A single row error halts the entire batch." },
"backToList": { "pl": "Wróć do katalogu", "en": "Back to catalog" },
"step": {
"download": { "pl": "Pobierz szablon", "en": "Download template" },
"upload": { "pl": "Wgraj plik", "en": "Upload file" },
"review": { "pl": "Sprawdź", "en": "Review" },
"confirm": { "pl": "Gotowe", "en": "Done" }
},
"download": {
"cardTitle": { "pl": "Szablon importu", "en": "Import template" },
"cardDescription": { "pl": "Pusty arkusz .xlsx z poprawnymi nagłówkami kolumn.", "en": "Blank .xlsx with the correct column headers." },
"cardButton": { "pl": "Pobierz szablon", "en": "Download template" }
},
"upload": {
"dropzoneTitle": { "pl": "Przeciągnij plik .xlsx lub kliknij, aby wybrać", "en": "Drag your .xlsx file here, or click to choose" },
"constraints": { "pl": "Maks. 5 MB, maks. 500 wierszy.", "en": "Up to 5 MB, up to 500 rows." }
},
"review": {
"loading": { "pl": "Trwa walidacja wgranego pliku…", "en": "Validating the uploaded file…" },
"success_one": { "pl": "Plik jest gotowy do importu ({{count}} pozycja).", "en": "File is ready to import ({{count}} item)." },
"success_few": { "pl": "Plik jest gotowy do importu ({{count}} pozycje).", "en": "" },
"success_other":{ "pl": "Plik jest gotowy do importu ({{count}} pozycji).", "en": "File is ready to import ({{count}} items)." },
"errorsTitle": { "pl": "Znaleziono błędy w wierszach", "en": "Errors found in rows" },
"errorsBody": { "pl": "Popraw plik i wgraj ponownie. Import jest wszystko-albo-nic.", "en": "Fix the file and upload again. Import is all-or-nothing." }
},
"confirm": {
"title": { "pl": "Import zakończony", "en": "Import complete" },
"body_one": { "pl": "Dodano {{count}} pozycję do katalogu.", "en": "{{count}} item added to the catalog." },
"body_few": { "pl": "Dodano {{count}} pozycje do katalogu.", "en": "" },
"body_other": { "pl": "Dodano {{count}} pozycji do katalogu.", "en": "{{count}} items added to the catalog." },
"goToCatalog": { "pl": "Otwórz katalog", "en": "Go to catalog" }
},
"error": {
"fileTooLarge": { "pl": "Plik jest za duży – maksymalnie 5 MB.", "en": "File is too large — 5 MB maximum." },
"tooManyRows": { "pl": "Plik zawiera za dużo wierszy – maksymalnie 500.", "en": "File has too many rows — 500 maximum." }
}
},
"itemGroupList": {
"title": { "pl": "Grupy pozycji", "en": "Item groups" },
"description": { "pl": "Organizacyjne kontenery dla pozycji jednego typu.", "en": "Organisational containers for items of one type." },
"createButton": { "pl": "Nowa grupa", "en": "New group" },
"filter": {
"searchPlaceholder": { "pl": "Szukaj grupy…", "en": "Search groups…" },
"type": { "pl": "Typ", "en": "Type" }
},
"stats": {
"total": { "pl": "Łącznie", "en": "Total" },
"empty": { "pl": "Puste", "en": "Empty" },
"byType": { "pl": "Według typu", "en": "By type" }
},
"column": {
"name": { "pl": "Nazwa grupy", "en": "Group name" },
"type": { "pl": "Typ", "en": "Type" },
"itemCount": { "pl": "Liczba pozycji", "en": "Items" }
},
"emptyAll": { "pl": "Brak grup", "en": "No groups yet" },
"emptyFiltered": { "pl": "Żadna grupa nie pasuje do filtrów", "en": "No group matches the filters" }
},
"itemGroupDetail": {
"backToList": { "pl": "Wróć do listy grup", "en": "Back to groups" },
"eyebrow": { "pl": "Grupa pozycji", "en": "Item group" },
"editButton": { "pl": "Edytuj grupę", "en": "Edit group" },
"deleteButton": { "pl": "Usuń grupę", "en": "Delete group" },
"section": {
"items_one": { "pl": "Pozycja w grupie", "en": "Item in this group" },
"items_few": { "pl": "Pozycje w grupie", "en": "" },
"items_other": { "pl": "Pozycji w grupie", "en": "Items in this group" },
"metadata": { "pl": "Metadane grupy", "en": "Group metadata" },
"audit": { "pl": "Audyt", "en": "Audit" }
},
"metadata": {
"name": { "pl": "Nazwa", "en": "Name" },
"type": { "pl": "Typ", "en": "Type" },
"itemCount": { "pl": "Liczba pozycji", "en": "Items" },
"deletable": { "pl": "Można usunąć", "en": "Deletable" },
"deletableYes": { "pl": "Tak (grupa jest pusta)", "en": "Yes (group is empty)" },
"deletableNo": { "pl": "Nie (grupa zawiera pozycje)", "en": "No (group has items)" }
},
"tableHeader": {
"addItem": { "pl": "Dodaj pozycję", "en": "Add item" },
"manage": { "pl": "Zarządzaj pozycjami", "en": "Manage items" }
},
"removeItem": {
"confirmTitle": { "pl": "Usunąć pozycję z grupy?", "en": "Remove item from this group?" },
"confirmBody": { "pl": "Pozycja sama nie zostanie usunięta – pozostanie w katalogu bez przypisania do grupy.", "en": "The item itself stays in the catalog — it just becomes ungrouped." },
"confirmCta": { "pl": "Usuń z grupy", "en": "Remove" }
},
"deleteGroup": {
"confirmTitle": { "pl": "Usunąć grupę?", "en": "Delete this group?" },
"confirmBody": { "pl": "Grupę można usunąć tylko gdy jest pusta. Powiązanych pozycji nie usuwa.", "en": "A group can be deleted only when empty. Linked items are not deleted." },
"confirmCta": { "pl": "Usuń grupę", "en": "Delete group" },
"hasMembersAlert": { "pl": "Najpierw usuń wszystkie pozycje z grupy.", "en": "Remove every item from this group first." }
}
},
"itemGroupForm": {
"createTitle": { "pl": "Nowa grupa", "en": "New group" },
"editTitle": { "pl": "Edytuj grupę", "en": "Edit group" },
"field": {
"name": { "pl": "Nazwa grupy", "en": "Group name" },
"type": { "pl": "Typ pozycji", "en": "Item type" },
"typeLockedHint": { "pl": "Typ ustawia się raz – nie można go później zmienić.", "en": "Type is set once — it cannot be changed later." }
}
}
},
"field": {
"code": { "pl": "Kod", "en": "Code" },
"name": { "pl": "Nazwa", "en": "Name" },
"itemType": { "pl": "Typ", "en": "Type" },
"storageUnit": { "pl": "Jednostka magazynowa", "en": "Storage unit" },
"description": { "pl": "Opis", "en": "Description" },
"tags": { "pl": "Tagi", "en": "Tags" },
"tagsHint": { "pl": "Do 20 tagów, każdy do 50 znaków.", "en": "Up to 20 tags, each up to 50 characters." },
"shelfLifeDays": { "pl": "Termin ważności (dni)", "en": "Shelf life (days)" },
"shelfLifeDaysHint":{ "pl": "Dotyczy tylko produktów i półproduktów.", "en": "Applies to products and intermediates only." },
"ean": { "pl": "Kod EAN", "en": "EAN" },
"group": { "pl": "Grupa", "en": "Group" },
"groupUngrouped": { "pl": "Bez grupy", "en": "Ungrouped" },
"status": { "pl": "Status", "en": "Status" }
},
"itemType": {
"ingredient": { "pl": "Surowiec", "en": "Ingredient" },
"product": { "pl": "Produkt", "en": "Product" },
"intermediate": { "pl": "Półprodukt", "en": "Intermediate" },
"packaging": { "pl": "Opakowanie", "en": "Packaging" },
"consumable": { "pl": "Materiał pomocniczy", "en": "Consumable" },
"service": { "pl": "Usługa", "en": "Service" },
"resale": { "pl": "Towar handlowy", "en": "Resale" }
},
"storageUnit": {
"kg": { "pl": "kg", "en": "kg" },
"g": { "pl": "g", "en": "g" },
"l": { "pl": "l", "en": "l" },
"ml": { "pl": "ml", "en": "ml" },
"piece": { "pl": "szt.", "en": "piece" }
},
"status": {
"active": { "pl": "Aktywna", "en": "Active" },
"inactive": { "pl": "Zarchiwizowana", "en": "Archived" },
"all": { "pl": "Wszystkie", "en": "All" }
},
"deactivateError": {
"stockRemaining": { "pl": "Nie można dezaktywować: na stanie magazynowym pozostała ilość większa od zera.", "en": "Cannot deactivate: stock on hand is greater than zero." },
"openProductionOrders": { "pl": "Nie można dezaktywować: pozycja występuje w otwartych zleceniach produkcyjnych.", "en": "Cannot deactivate: the item is referenced by open production orders." },
"inPublishedBom": { "pl": "Nie można dezaktywować: pozycja występuje w opublikowanych recepturach (BOM).", "en": "Cannot deactivate: the item is part of published recipe BOMs." },
"openSalesOrders": { "pl": "Nie można dezaktywować: pozycja występuje w otwartych zleceniach sprzedaży.", "en": "Cannot deactivate: the item is referenced by open sales orders." }
},
"toast": {
"created": { "pl": "Pozycja dodana", "en": "Item created" },
"updated": { "pl": "Pozycja zaktualizowana", "en": "Item updated" },
"updatedPartial": { "pl": "Zapisano z błędami – sprawdź wyróżnione sekcje.", "en": "Saved with errors — review highlighted sections." },
"updateFailed": { "pl": "Nie udało się zapisać pozycji.", "en": "Failed to save item." },
"deactivated": { "pl": "Pozycja zdezaktywowana", "en": "Item deactivated" },
"reactivated": { "pl": "Pozycja aktywowana ponownie", "en": "Item reactivated" },
"removedFromGroup": { "pl": "Pozycję usunięto z grupy", "en": "Item removed from group" },
"groupCreated": { "pl": "Grupa utworzona", "en": "Group created" },
"groupUpdated": { "pl": "Grupa zaktualizowana", "en": "Group updated" },
"groupDeleted": { "pl": "Grupa usunięta", "en": "Group deleted" }
}
}
Notes for implementers¶
- The JSON above is the proposed shape for
src/locales/{en,pl}/item-catalog.json. Each leaf currently holds an{ pl, en }pair only for review readability — in the actual locale files, English lives insrc/locales/en/item-catalog.jsonand Polish insrc/locales/pl/item-catalog.json, each as a flat per-language JSON tree mirroring the structure above. Use Polish drafts as the starting point but treat them as drafts: validate against the rendered UI during the M1+ tickets and refine. - Polish empty strings on the
_fewplural rules above mean the English entry is identical to_other(English has no_few); the JSON files will simply omit_fewforen. - The seed translation set for M0 ticket 12 is the union of the leaves under
page.itemList,page.itemGroupList,field.*,itemType.*,storageUnit.*,status.*,toast.*,deactivateError.*. Other keys are added by their owning ticket. - Sidebar labels (
common:nav.catalog,common:nav.items,common:nav.groups) live insrc/locales/{en,pl}/common.json— added during M0 ticket 12 alongside the NavItems. t('item-catalog:itemType.${item.item_type}')andt('item-catalog:storageUnit.${item.storage_unit}')are the canonical patterns for translating BE enum values — never hand-writeswitch (item.item_type)strings.