@vtex/faststore-plugin-buyer-portal 2.0.13 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/cms/b2b/components/.gitkeep +0 -0
- package/cms/b2b/pages/cms_content_type__i18n.jsonc +36 -0
- package/cypress/integration/localization.test.ts +482 -0
- package/cypress/support/commands.ts +8 -0
- package/docs/i18n/i18n-messages-format.md +391 -0
- package/docs/i18n/locales.json +701 -0
- package/docs/i18n/migration-inventory.md +959 -0
- package/package.json +3 -3
- package/specs/custom-localization-system.md +711 -0
- package/specs/multi-locale-e2e-validation.md +277 -0
- package/src/__mocks__/discovery.config.ts +12 -0
- package/src/features/accounting-fields/components/AccountingConfirmDelete/AccountingConfirmDelete.tsx +14 -4
- package/src/features/accounting-fields/components/AccountingFieldConfirmEdit/AccountingFieldConfirmEdit.tsx +11 -6
- package/src/features/accounting-fields/components/AccountingFieldDropdown/AccountingFieldDropdown.tsx +5 -3
- package/src/features/accounting-fields/components/AccountingFieldForm/AccountingFieldForm.tsx +33 -23
- package/src/features/accounting-fields/components/AccountingValueConfirmEdit/AccountingValueConfirmEdit.tsx +10 -6
- package/src/features/accounting-fields/components/AccountingValueDropdown/AccountingValueDropdown.tsx +19 -9
- package/src/features/accounting-fields/components/AccountingValueForm/AccountingValueForm.tsx +21 -10
- package/src/features/accounting-fields/components/CreateAccountingFieldDrawer/CreateAccountingFieldDrawer.tsx +11 -8
- package/src/features/accounting-fields/components/CreateAccountingValueDrawer/CreateAccountingValueDrawer.tsx +18 -7
- package/src/features/accounting-fields/components/DeleteAccountingFieldDrawer/DeleteAccountingFieldDrawer.tsx +14 -12
- package/src/features/accounting-fields/components/DeleteAccountingValueDrawer/DeleteAccountingValueDrawer.tsx +20 -10
- package/src/features/accounting-fields/components/EditAccountingFieldDrawer/EditAccountingFieldDrawer.tsx +5 -3
- package/src/features/accounting-fields/components/EditAccountingValueDrawer/EditAccountingValueDrawer.tsx +13 -6
- package/src/features/accounting-fields/components/RemoveValueFromUnitDrawer/RemoveValueFromUnitDrawer.tsx +19 -8
- package/src/features/accounting-fields/layouts/AccountingFieldLayout/AccountingFieldLayout.tsx +11 -9
- package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +13 -7
- package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +22 -20
- package/src/features/addresses/components/AddressForm/AddressForm.tsx +33 -22
- package/src/features/addresses/components/AddressLine/AddressLine.tsx +29 -25
- package/src/features/addresses/components/AddressLocationsList/AddressLocationsList.tsx +15 -6
- package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +9 -4
- package/src/features/addresses/components/AddressesCard/AddressesCard.tsx +8 -5
- package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +27 -14
- package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +26 -21
- package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +20 -16
- package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +26 -14
- package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +20 -17
- package/src/features/addresses/components/EditAddressDrawer/EditAddressDrawer.tsx +12 -6
- package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +24 -17
- package/src/features/addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx +17 -15
- package/src/features/addresses/components/LocationForm/LocationField/LocationField.tsx +8 -5
- package/src/features/addresses/components/LocationForm/LocationForm.tsx +9 -3
- package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +15 -9
- package/src/features/addresses/components/RecipientsForm/RecipientItem/RecipientItem.tsx +55 -46
- package/src/features/addresses/components/RecipientsForm/RecipientsForm.tsx +4 -1
- package/src/features/addresses/components/RemoveAddressDrawer/RemoveAddressDrawer.tsx +21 -14
- package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +16 -9
- package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +11 -8
- package/src/features/b2b-agent/layouts/B2BAgentLayout/B2BAgentLayout.tsx +3 -1
- package/src/features/budgets/components/BudgetAddForm/BudgetAddForm.tsx +14 -12
- package/src/features/budgets/components/BudgetAddSuccess/BudgetAddSuccess.tsx +16 -14
- package/src/features/budgets/components/BudgetAllocationsSelection/BudgetAllocationsSelection.tsx +8 -5
- package/src/features/budgets/components/BudgetAllocationsTable/BudgetAllocationsTable.tsx +9 -6
- package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +23 -17
- package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +13 -6
- package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +31 -24
- package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +15 -9
- package/src/features/budgets/components/BudgetRemainingBalance/BudgetRemainingBalance.tsx +6 -2
- package/src/features/budgets/components/BudgetSettingsInfo/BudgetSettingsInfo.tsx +27 -10
- package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +14 -7
- package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +11 -8
- package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +8 -5
- package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +31 -18
- package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +17 -12
- package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +14 -8
- package/src/features/budgets/components/EmptyState.tsx/EmptyState.tsx +4 -1
- package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +7 -5
- package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +5 -3
- package/src/features/budgets/messages/budgetNotifications.ts +5 -5
- package/src/features/budgets/utils/handleBudgetError.tsx +5 -8
- package/src/features/budgets/utils/handleBudgetError.types.ts +3 -0
- package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingFieldCriteriaSelector.tsx +5 -3
- package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingValueCriteriaSelector.tsx +20 -9
- package/src/features/buying-policies/components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer.tsx +8 -5
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +49 -33
- package/src/features/buying-policies/components/BudgetCriteriaSelector/BudgetCriteriaSelector.tsx +8 -2
- package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +5 -3
- package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +28 -24
- package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +10 -7
- package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +11 -4
- package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +26 -9
- package/src/features/buying-policies/utils/accountingFieldCriteriaHelpers.ts +2 -2
- package/src/features/buying-policies/utils/buyingPoliciesWorkflowTypes.ts +3 -3
- package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +8 -8
- package/src/features/buying-policies/utils/spendingLimitsCriteriaOptions.ts +6 -6
- package/src/features/contracts/components/ContractsCard/ContractsCard.tsx +7 -5
- package/src/features/contracts/layouts/ContractsLayout/ContractsLayout.tsx +9 -4
- package/src/features/credit-cards/components/CreateCreditCardDrawer/CreateCreditCardDrawer.tsx +17 -11
- package/src/features/credit-cards/components/CreditCardDropdownMenu/CreditCardDropdownMenu.tsx +17 -11
- package/src/features/credit-cards/components/CreditCardForm/CreditCardForm.tsx +8 -5
- package/src/features/credit-cards/components/CreditCardSettingsDrawer/CreditCardSettingsDrawer.tsx +10 -8
- package/src/features/credit-cards/components/DeleteCreditCardDrawer/DeleteCreditCardDrawer.tsx +19 -14
- package/src/features/credit-cards/components/EditCreditCardDrawer/EditCreditCardDrawer.tsx +14 -13
- package/src/features/credit-cards/components/RemoveCreditCardDrawer/RemoveCreditCardDrawer.tsx +18 -15
- package/src/features/credit-cards/layouts/CreditCardsLayout/CreditCardLayout.tsx +10 -4
- package/src/features/order-entry/layouts/OrderEntryLayout.tsx +3 -1
- package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +11 -9
- package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +28 -16
- package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +18 -12
- package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +28 -29
- package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbLink.tsx +4 -4
- package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/OrgUnitDetailsDropdownMenu.tsx +4 -2
- package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +23 -5
- package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +18 -2
- package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +6 -4
- package/src/features/org-units/components/OrgUnitsHierarchyTree/OrgUnitsHierarchyTree.tsx +9 -6
- package/src/features/org-units/components/OrganizationalUnitsCard/OrganizationalUnitsCard.tsx +15 -7
- package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +15 -9
- package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +27 -26
- package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +8 -3
- package/src/features/payment-methods/components/AddPaymentMethodsDrawer/AddPaymentMethodsDrawer.tsx +29 -18
- package/src/features/payment-methods/components/PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer.tsx +13 -9
- package/src/features/payment-methods/components/RemoveMethodButton/RemoveMethodButton.tsx +5 -2
- package/src/features/payment-methods/components/RemovePaymentMethodsDrawer/RemovePaymentMethodsDrawer.tsx +10 -7
- package/src/features/payment-methods/hooks/useRemovePaymentMethodSubmit.ts +6 -4
- package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +21 -15
- package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +11 -7
- package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +9 -6
- package/src/features/profile/components/ProfileCard/ProfileCard.tsx +8 -5
- package/src/features/profile/components/ProfileSummary/ProfileSummary.tsx +13 -6
- package/src/features/profile/layouts/ProfileLayout/ProfileLayout.tsx +16 -6
- package/src/features/roles/layout/RoleDetailsLayout/RoleDetailsLayout.tsx +4 -2
- package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +5 -3
- package/src/features/shared/components/BasicCard/BasicCard.tsx +6 -4
- package/src/features/shared/components/BasicCard/BasicCardLine.tsx +5 -4
- package/src/features/shared/components/CustomerSwitch/CustomerSwitchDrawer.tsx +10 -2
- package/src/features/shared/components/CustomerSwitch/CustomerSwitchSearch.tsx +4 -1
- package/src/features/shared/components/DropdownFilter/DropdownFilter.tsx +11 -7
- package/src/features/shared/components/Error/Error.tsx +13 -6
- package/src/features/shared/components/HeaderInside/HeaderInside.tsx +5 -2
- package/src/features/shared/components/InternalSearch/InternalSearch.tsx +4 -1
- package/src/features/shared/components/InternalTopbar/InternalTopbar.tsx +7 -5
- package/src/features/shared/components/MainLinksDropdownMenu/MainLinksDropdownMenu.tsx +23 -8
- package/src/features/shared/components/OrgUnitInputSearch/OrgUnitInputSearch.tsx +7 -2
- package/src/features/shared/components/Pagination/Pagination.tsx +6 -2
- package/src/features/shared/components/Paginator/Counter.tsx +11 -5
- package/src/features/shared/components/Paginator/Interval.tsx +4 -3
- package/src/features/shared/components/Paginator/NextPageButton.tsx +6 -2
- package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +6 -4
- package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +11 -10
- package/src/features/shared/components/SettingsDrawer/listTypeOptions.ts +10 -7
- package/src/features/shared/components/SortFilter/SortFilter.tsx +15 -8
- package/src/features/shared/components/Table/TableRow/TableRow.tsx +5 -1
- package/src/features/shared/components/VerticalNav/VerticalNavLink.tsx +7 -3
- package/src/features/shared/layouts/BaseTabsLayout/Navbar.tsx +28 -4
- package/src/features/shared/layouts/BaseTabsLayout/about-drawer/AboutDrawer.tsx +15 -5
- package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +24 -9
- package/src/features/shared/layouts/BaseTabsLayout/sidebar-drawer/SidebarDrawer.tsx +5 -2
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +10 -5
- package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +7 -4
- package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +9 -4
- package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +7 -4
- package/src/features/shared/layouts/OrgUnitTabsLayout/OrgUnitTabLayout.tsx +3 -0
- package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +13 -7
- package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +9 -5
- package/src/features/shared/localization/LocalizationContext.tsx +35 -0
- package/src/features/shared/localization/__tests__/createLocalize.test.ts +166 -0
- package/src/features/shared/localization/__tests__/get-localization-labels.service.test.ts +202 -0
- package/src/features/shared/localization/clients/LocalizationClient.ts +19 -0
- package/src/features/shared/localization/constants.ts +31 -0
- package/src/features/shared/localization/createLocalize.ts +78 -0
- package/src/features/shared/localization/services/get-localization-labels.service.ts +76 -0
- package/src/features/shared/localization/types.ts +66 -0
- package/src/features/shared/localization/withLocalization.tsx +22 -0
- package/src/features/shared/types/AuthRouteProps.ts +5 -0
- package/src/features/shared/types/LoaderData.ts +1 -0
- package/src/features/shared/utils/addresLabelToPropMapping.ts +10 -10
- package/src/features/shared/utils/constants.ts +1 -4
- package/src/features/shared/utils/getContractSettingsLinks.ts +17 -12
- package/src/features/shared/utils/getFinanceSettingsLinks.ts +14 -9
- package/src/features/shared/utils/getKeyByValue.ts +2 -2
- package/src/features/shared/utils/getOrganizationSettingsLinks.ts +13 -3
- package/src/features/shared/utils/getSumaListingSidebarLinks.ts +15 -10
- package/src/features/shared/utils/index.ts +2 -0
- package/src/features/shared/utils/routeLayoutMapping.ts +30 -30
- package/src/features/shared/utils/search.tsx +10 -0
- package/src/features/shared/utils/withAuth.tsx +6 -0
- package/src/features/shared/utils/withAuthLoader.ts +11 -0
- package/src/features/shared/utils/withLoaderErrorBoundary.ts +9 -0
- package/src/features/shared/utils/withProviders.tsx +13 -2
- package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +39 -27
- package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +41 -39
- package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +27 -23
- package/src/features/users/components/EmailTransactionalSection/EmailTransactionalSection.tsx +6 -8
- package/src/features/users/components/LoginField/LoginField.tsx +4 -4
- package/src/features/users/components/ReassignOrgUnitDrawer/ReassignOrgUnitDrawer.tsx +22 -19
- package/src/features/users/components/ResetPasswordDrawer/ResetPasswordDrawer.tsx +16 -21
- package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +26 -15
- package/src/features/users/components/UpdateUserDrawerWithUsername/UpdateUserDrawerWithUsername.tsx +22 -8
- package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +7 -5
- package/src/features/users/components/UserFormFields/UserFormFields.tsx +9 -8
- package/src/features/users/components/UsersCard/UsersCard.tsx +4 -2
- package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +26 -10
- package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +12 -8
- package/src/pages/accounting-field.tsx +28 -15
- package/src/pages/address-details.tsx +37 -26
- package/src/pages/addresses.tsx +27 -12
- package/src/pages/budgets-details.tsx +25 -15
- package/src/pages/budgets.tsx +31 -17
- package/src/pages/buyer-organization-manager.tsx +23 -12
- package/src/pages/buying-policies.tsx +38 -20
- package/src/pages/buying-policy-details.tsx +20 -19
- package/src/pages/contracts.tsx +15 -1
- package/src/pages/credit-cards.tsx +30 -17
- package/src/pages/order-entry.tsx +26 -15
- package/src/pages/org-unit-details.tsx +20 -6
- package/src/pages/org-units.tsx +20 -6
- package/src/pages/payment-groups.tsx +15 -12
- package/src/pages/productAssortment.tsx +10 -1
- package/src/pages/profile.tsx +14 -1
- package/src/pages/role-details.tsx +19 -6
- package/src/pages/roles.tsx +27 -14
- package/src/pages/user-details.tsx +29 -17
- package/src/pages/users.tsx +11 -0
- package/vitest.config.ts +13 -0
- package/src/features/addresses/constants/messages.ts +0 -8
- package/src/features/credit-cards/constants/messages.ts +0 -6
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# i18n Message Catalog Format
|
|
2
|
+
|
|
3
|
+
> Reference document describing the structure and conventions used by the
|
|
4
|
+
> internationalization (i18n) message catalogs in `src/messages/*.json`. This
|
|
5
|
+
> document is intended to be reused as **agent context** in other projects to:
|
|
6
|
+
>
|
|
7
|
+
> 1. Extract user-facing strings from a codebase, and
|
|
8
|
+
> 2. Produce message catalog files that follow the exact same format.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Overview
|
|
13
|
+
|
|
14
|
+
The project uses [`react-intl`](https://formatjs.io/docs/react-intl/) (FormatJS).
|
|
15
|
+
All translatable strings live in flat JSON files under `src/messages/`, one file
|
|
16
|
+
per locale. Today the project ships:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/messages/
|
|
20
|
+
├── en-US.json ← source / fallback locale
|
|
21
|
+
└── pt-BR.json ← target locale
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Both files share the **exact same set of keys**. The values are the localized
|
|
25
|
+
strings.
|
|
26
|
+
|
|
27
|
+
## 2. File Format
|
|
28
|
+
|
|
29
|
+
Each catalog is a **flat JSON object** (one level deep). Keys are
|
|
30
|
+
dot-separated namespaces; values are the localized strings.
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"shared.buttons.save": "Save",
|
|
35
|
+
"shared.buttons.cancel": "Cancel",
|
|
36
|
+
"addresses.forms.country": "Country",
|
|
37
|
+
"addresses.validation.streetRequired": "Street Address is required",
|
|
38
|
+
"users.delete.question": "Permanently delete {userName}?"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> Important: Do **NOT** use nested objects (e.g. `{ "shared": { "buttons": {…} } }`).
|
|
43
|
+
> The format is intentionally flat — `react-intl` looks up messages by the full
|
|
44
|
+
> dotted string id.
|
|
45
|
+
|
|
46
|
+
### Formatting rules
|
|
47
|
+
|
|
48
|
+
- File is UTF-8 JSON, ends with a single trailing newline.
|
|
49
|
+
- Two-space indentation.
|
|
50
|
+
- Keys are quoted strings; the entire id (including dots) is a single key.
|
|
51
|
+
- All catalogs (locales) MUST share the same set of keys.
|
|
52
|
+
- Strings can span multiple lines in source if needed; tooling preserves them
|
|
53
|
+
as a single string. Keep them on a single line whenever practical.
|
|
54
|
+
|
|
55
|
+
## 3. Key Naming Convention
|
|
56
|
+
|
|
57
|
+
Each id is composed of **2 to 4 dot-separated segments**:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
<feature>.<group>[.<sub-group>].<name>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Segment | Description | Casing |
|
|
64
|
+
|---------|-------------|--------|
|
|
65
|
+
| `feature` | Top-level feature/domain (e.g. `addresses`, `users`, `shared`) | `camelCase` |
|
|
66
|
+
| `group` | Logical grouping inside the feature (e.g. `buttons`, `forms`, `toast`, `drawers`) | `camelCase` |
|
|
67
|
+
| `sub-group` *(optional)* | Further specialization, e.g. component name, sub-flow, action | `camelCase` |
|
|
68
|
+
| `name` | The specific string identifier | `camelCase` |
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
shared.buttons.save → 3 segments
|
|
74
|
+
addresses.validation.streetRequired → 3 segments
|
|
75
|
+
addresses.components.deleteAddress.title → 4 segments (sub-group = component)
|
|
76
|
+
users.createWithUsername.sharedEmailBody1 → 3 segments (sub-group baked into group)
|
|
77
|
+
buyingPolicies.criteria.spending.orderValueGtX → 4 segments
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Casing notes
|
|
81
|
+
|
|
82
|
+
- The codebase is **predominantly camelCase** for all segments.
|
|
83
|
+
- A small amount of legacy keys use kebab-case for the feature segment
|
|
84
|
+
(`buying-policies.*`, `credit-cards.*`, `b2b-agent.*`, `org-units.*`). These
|
|
85
|
+
duplicate the camelCase versions (`buyingPolicies.*`, `creditCards.*`, etc.)
|
|
86
|
+
and exist for backwards compatibility. **New keys MUST use camelCase.**
|
|
87
|
+
|
|
88
|
+
## 4. Top-Level Feature Namespaces
|
|
89
|
+
|
|
90
|
+
The `feature` segment maps to a feature folder under `src/features/` (or to a
|
|
91
|
+
cross-cutting concern). The catalog observed in this project uses the namespaces
|
|
92
|
+
below — when introducing new strings, prefer reusing one of these instead of
|
|
93
|
+
inventing a new namespace.
|
|
94
|
+
|
|
95
|
+
| Namespace | Scope |
|
|
96
|
+
|-----------|-------|
|
|
97
|
+
| `shared` | Cross-cutting strings reused everywhere (buttons, generic labels, errors, generic drawer copy, pagination, validation, region selector, etc.) |
|
|
98
|
+
| `addresses` | Addresses feature (forms, locations, recipients, address settings) |
|
|
99
|
+
| `roles` | Roles & permissions (role names, permission labels & descriptions) |
|
|
100
|
+
| `orgUnits` | Organizational units feature (CRUD, hierarchy, authentication setup) |
|
|
101
|
+
| `orgUnitDetails` | Org-unit details page (links, footer, action menus) |
|
|
102
|
+
| `phone` | Phone-input shared component (countries, errors, labels) |
|
|
103
|
+
| `buyingPolicies` | Buying policies feature (CRUD, criteria, workflow, actions) |
|
|
104
|
+
| `creditCards` | Credit cards feature (CRUD, settings, toasts) |
|
|
105
|
+
| `paymentMethods` | Payment methods feature (CRUD, settings, removal flow) |
|
|
106
|
+
| `productAssortment` | Product assortment / collections feature |
|
|
107
|
+
| `budgets` | Budgets feature (CRUD, allocations, notifications, success screen) |
|
|
108
|
+
| `users` | Users feature (CRUD, reset password, reassign org unit, username flow) |
|
|
109
|
+
| `customFields` | Generic custom-field settings/toasts (PO numbers, cost centers, releases) |
|
|
110
|
+
| `contracts` | Contracts feature |
|
|
111
|
+
| `profile` | Profile feature |
|
|
112
|
+
| `layouts` | Strings owned by page layout components (titles, empty states, table columns) |
|
|
113
|
+
|
|
114
|
+
## 5. Common Group Patterns
|
|
115
|
+
|
|
116
|
+
The second segment (and sometimes third) follows recurring patterns. Use the
|
|
117
|
+
table below as a controlled vocabulary when categorizing strings.
|
|
118
|
+
|
|
119
|
+
| Group | Used for | Examples |
|
|
120
|
+
|-------|----------|----------|
|
|
121
|
+
| `buttons` | Button labels (`Save`, `Cancel`, `Confirm`, `Apply`) | `shared.buttons.save`, `shared.buttons.cancel` |
|
|
122
|
+
| `labels` | Generic short UI labels not tied to a form input | `shared.labels.required`, `shared.labels.copy` |
|
|
123
|
+
| `messages` | Longer informational messages / confirmations | `shared.messages.confirmDelete`, `shared.messages.unsavedChanges` |
|
|
124
|
+
| `errors` | Error copy (titles, hints) — distinct from validation | `shared.errors.somethingWrong`, `shared.errors.tryAgain` |
|
|
125
|
+
| `forms` | Form field labels & section headings | `addresses.forms.country`, `budgets.forms.startDate` |
|
|
126
|
+
| `validation` | Inline validation / required-field messages | `addresses.validation.cityRequired`, `users.create.emailRequired` |
|
|
127
|
+
| `placeholder` / `placeholders` | Input placeholder text | `budgets.placeholders.currency`, `buyingPolicies.placeholder.criteriaExample` |
|
|
128
|
+
| `tooltip` / `tooltips` | Tooltip / help text | `budgets.tooltip.autoRenew`, `buyingPolicies.tooltip.description` |
|
|
129
|
+
| `aria` | Accessibility labels for non-text controls | `addresses.aria.removeLocationField`, `paymentMethods.aria.selectAll` |
|
|
130
|
+
| `toast` / `toasts` | Toast notifications (success / error / progress) | `users.toast.userAddedSuccess`, `creditCards.toast.defaultError` |
|
|
131
|
+
| `drawer` / `drawers` | Drawer titles, headings and shared drawer copy | `shared.drawer.addTitle`, `creditCards.drawers.deleteCard` |
|
|
132
|
+
| `dropdown` | Dropdown menu items | `users.dropdown.editDetails`, `buyingPolicies.dropdown.open` |
|
|
133
|
+
| `pagination` | Pagination controls | `shared.pagination.counter`, `users.pagination.loadMore` |
|
|
134
|
+
| `navigation` | Top-level navigation labels | `shared.navigation.contract`, `shared.navigation.organization` |
|
|
135
|
+
| `actions` | Verbs / action menu items | `shared.actions.rename`, `shared.actions.setAsDefault` |
|
|
136
|
+
| `links` | Link text | `orgUnitDetails.links.profile` |
|
|
137
|
+
| `titles` | Page / section titles (when not better placed under `forms`/`drawer`) | `shared.titles.switchCustomer` |
|
|
138
|
+
| `countries` | Country names | `addresses.countries.us`, `phone.countries.br` |
|
|
139
|
+
| `currencies` | Currency labels | `budgets.currencies.usd` |
|
|
140
|
+
| `regions` | Locale region labels | `shared.regions.enUS` |
|
|
141
|
+
| `permissions` / `permissionDescriptions` / `roleNames` / `descriptions` | Role-related vocab | `roles.permissions.create` |
|
|
142
|
+
| `create` / `update` / `delete` / `edit` / `reassign` / `reset` / `remove` | Action-flow groupings (form fields, toasts, copy specific to that flow) | `users.create.fullNameLabel`, `users.delete.question` |
|
|
143
|
+
| `criteria` / `workflow` / `details` / `settings` / `settingsInfo` / `notificationsForm` / `notificationsInfo` | Sub-flows of a feature | `buyingPolicies.criteria.spending.unitPriceGtX` |
|
|
144
|
+
| `components` | Container for component-scoped strings (4-segment ids) | `addresses.components.deleteAddress.title` |
|
|
145
|
+
| `table` / `usersTable` / `allocationsTable` | Table column headers and row actions | `budgets.table.columnRemaining`, `budgets.usersTable.columnEmail` |
|
|
146
|
+
| `empty` | Empty-state copy | `budgets.empty.noAllocations`, `layouts.addresses.empty` |
|
|
147
|
+
|
|
148
|
+
## 6. ICU MessageFormat Placeholders
|
|
149
|
+
|
|
150
|
+
Strings can include named placeholders enclosed in single braces. They are
|
|
151
|
+
substituted at render time via the `values` prop / argument.
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
"users.delete.question": "Permanently delete {userName}?",
|
|
155
|
+
"shared.pagination.counter": "{items} of {total}",
|
|
156
|
+
"buyingPolicies.workflow.levelLabel": "Level {n}",
|
|
157
|
+
"budgets.settingsInfo.periodRange": "{start} to {end}",
|
|
158
|
+
"customFields.settings.intro": "Set how buyers in this unit should enter {fieldPlural} during checkout"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Conventions for placeholder names
|
|
162
|
+
|
|
163
|
+
- Use **camelCase** (`userName`, `orgUnitName`, `entityPlural`).
|
|
164
|
+
- Reuse names that already exist for the same concept across the catalog:
|
|
165
|
+
- `{name}` — generic entity name
|
|
166
|
+
- `{n}` — ordinal index (e.g. `Level 2`, `Location 3`)
|
|
167
|
+
- `{count}` — integer count
|
|
168
|
+
- `{label}`, `{field}`, `{fieldPlural}` — dynamic field/entity label
|
|
169
|
+
- `{entityName}`, `{entityPlural}` — dynamic entity label
|
|
170
|
+
- `{userName}`, `{orgUnitName}`, `{addressName}`, `{cardName}`,
|
|
171
|
+
`{paymentMethodName}`, `{productAssortmentName}`,
|
|
172
|
+
`{previousOrgUnitName}`, `{existingUserName}` — identifying names
|
|
173
|
+
- `{email}`, `{emailLink}` — emails
|
|
174
|
+
- `{items}`, `{shown}`, `{total}` — pagination
|
|
175
|
+
- `{used}`, `{total}` — amounts (with same name as pagination total)
|
|
176
|
+
- `{start}`, `{end}` — period ranges
|
|
177
|
+
- `{max}` — threshold count
|
|
178
|
+
|
|
179
|
+
> All placeholders that appear in the source string MUST also appear in the
|
|
180
|
+
> translated string in every locale.
|
|
181
|
+
|
|
182
|
+
## 7. Rich-Text Tags
|
|
183
|
+
|
|
184
|
+
Strings may include simple XML-like tags for inline rich content. They are NOT
|
|
185
|
+
HTML — they are pseudo-tags resolved by `react-intl` via the `values` prop,
|
|
186
|
+
where the tag name maps to a function that wraps `chunks`.
|
|
187
|
+
|
|
188
|
+
```jsonc
|
|
189
|
+
"buyingPolicies.form.criteriaIntro": "Define the conditions that will trigger this buying policy using <link>JSONata</link>",
|
|
190
|
+
"budgets.addSuccess.descriptionSecond": "You can edit or remove this policy later in the <link>Buying Policies</link> section."
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Rendered with:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
<FormattedMessage
|
|
197
|
+
id="buyingPolicies.form.criteriaIntro"
|
|
198
|
+
defaultMessage="Define the conditions that will trigger this buying policy using <link>JSONata</link>"
|
|
199
|
+
values={{
|
|
200
|
+
link: (chunks) => <a href="https://jsonata.org/" target="_blank" rel="noreferrer">{chunks}</a>,
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The only tag in active use is `<link>`. New tags should be:
|
|
206
|
+
|
|
207
|
+
- All-lowercase, single word (`<bold>`, `<strong>`, `<link>`).
|
|
208
|
+
- Closed properly (`<tag>...</tag>`).
|
|
209
|
+
- Documented in this file when introduced.
|
|
210
|
+
|
|
211
|
+
## 8. Usage in Code (`react-intl`)
|
|
212
|
+
|
|
213
|
+
Two equivalent APIs are used. Both rely on the message id existing in every
|
|
214
|
+
locale catalog.
|
|
215
|
+
|
|
216
|
+
### 8.1 Imperative — `useIntl().formatMessage`
|
|
217
|
+
|
|
218
|
+
Use when a string is consumed as a plain JavaScript value (props, toast
|
|
219
|
+
messages, `aria-label`, dynamic concatenation):
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { useIntl } from "react-intl";
|
|
223
|
+
|
|
224
|
+
const intl = useIntl();
|
|
225
|
+
|
|
226
|
+
pushToast({
|
|
227
|
+
message: intl.formatMessage({
|
|
228
|
+
id: "addresses.components.addRecipientsDrawer.toastSuccess",
|
|
229
|
+
defaultMessage: "Recipients added successfully",
|
|
230
|
+
}),
|
|
231
|
+
status: "INFO",
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
With placeholders:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
intl.formatMessage(
|
|
239
|
+
{ id: "users.delete.question", defaultMessage: "Permanently delete {userName}?" },
|
|
240
|
+
{ userName: user.fullName },
|
|
241
|
+
);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 8.2 Declarative — `<FormattedMessage>`
|
|
245
|
+
|
|
246
|
+
Use when the string is rendered directly in JSX, especially when it contains
|
|
247
|
+
rich-text tags or React-element placeholders:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { FormattedMessage } from "react-intl";
|
|
251
|
+
|
|
252
|
+
<FormattedMessage
|
|
253
|
+
id="users.create.userAlreadyAssigned.body2"
|
|
254
|
+
defaultMessage="Users can only belong to one organizational unit at a time. To add {userName} to {orgUnitName}, go to their profile and move them from {previousOrgUnitName}."
|
|
255
|
+
values={{
|
|
256
|
+
userName: <strong>{userName}</strong>,
|
|
257
|
+
orgUnitName: <strong>{orgUnitName}</strong>,
|
|
258
|
+
previousOrgUnitName: <strong>{previousOrgUnitName}</strong>,
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 8.3 The `defaultMessage` field
|
|
264
|
+
|
|
265
|
+
- Always provide `defaultMessage` alongside `id`. It MUST be byte-equivalent
|
|
266
|
+
to the value in the source-of-truth locale (`en-US.json`).
|
|
267
|
+
- Tooling can use `defaultMessage` to detect drift between code and catalogs.
|
|
268
|
+
|
|
269
|
+
## 9. Authoring & Extraction Workflow
|
|
270
|
+
|
|
271
|
+
When porting this format to another project, an agent should follow this
|
|
272
|
+
process:
|
|
273
|
+
|
|
274
|
+
### Step 1 — Identify user-facing strings
|
|
275
|
+
|
|
276
|
+
Search the codebase for strings that are visible to end users. Common signals:
|
|
277
|
+
|
|
278
|
+
- JSX text nodes: `<button>Save</button>`
|
|
279
|
+
- Component props that render text: `title="Add user"`, `placeholder="Search"`,
|
|
280
|
+
`aria-label="Close drawer"`
|
|
281
|
+
- String literals passed to UI helpers: `pushToast({ message: "..." })`,
|
|
282
|
+
`alert("...")`, `confirm("...")`
|
|
283
|
+
- Validation/error helpers: `setError("...")`, `throw new Error("...")` *(only
|
|
284
|
+
if surfaced to the user — internal exceptions should NOT be translated)*
|
|
285
|
+
- Constants files used to render UI (e.g. menu definitions)
|
|
286
|
+
|
|
287
|
+
Do **not** translate:
|
|
288
|
+
|
|
289
|
+
- Logger / analytics event strings
|
|
290
|
+
- Test fixtures and Cypress selectors
|
|
291
|
+
- Code comments
|
|
292
|
+
- API field names / GraphQL fragment text
|
|
293
|
+
|
|
294
|
+
### Step 2 — Categorize each string
|
|
295
|
+
|
|
296
|
+
Pick a `feature` namespace (Section 4) and a `group` (Section 5). Reuse
|
|
297
|
+
existing groups before introducing new ones. Prefer placing reusable, generic
|
|
298
|
+
strings (`Save`, `Cancel`, `Loading...`) under `shared.*`.
|
|
299
|
+
|
|
300
|
+
### Step 3 — Generate a stable id
|
|
301
|
+
|
|
302
|
+
Build the id as `<feature>.<group>[.<subGroup>].<name>` with all segments in
|
|
303
|
+
camelCase. Make `name` short, descriptive, and unique within its group. Examples:
|
|
304
|
+
|
|
305
|
+
| Source string | Where it lives | Suggested id |
|
|
306
|
+
|---------------|----------------|--------------|
|
|
307
|
+
| `Save` (generic) | any button | `shared.buttons.save` |
|
|
308
|
+
| `Add address` | `CreateAddressDrawer` title | `addresses.components.createAddress.title` |
|
|
309
|
+
| `An error occurred while creating the address` | toast | `addresses.components.createAddress.addressCreateError` |
|
|
310
|
+
| `Permanently delete {name}?` | delete drawer | `<feature>.delete.permanentDeleteQuestion` |
|
|
311
|
+
| `Permissions` | role details heading | `roles.permissionsTitle` |
|
|
312
|
+
| `Loading...` | generic spinner | `shared.labels.loading` |
|
|
313
|
+
|
|
314
|
+
### Step 4 — Replace the literal in code
|
|
315
|
+
|
|
316
|
+
Pick the API based on usage (Section 8):
|
|
317
|
+
|
|
318
|
+
```diff
|
|
319
|
+
- <button onClick={onSave}>Save</button>
|
|
320
|
+
+ <button onClick={onSave}>
|
|
321
|
+
+ <FormattedMessage id="shared.buttons.save" defaultMessage="Save" />
|
|
322
|
+
+ </button>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Step 5 — Update every locale catalog
|
|
326
|
+
|
|
327
|
+
Add the new id to `src/messages/en-US.json` (source of truth) **and** to every
|
|
328
|
+
other locale file with a localized value. Catalogs MUST stay in sync — missing
|
|
329
|
+
keys in a non-source locale fall back silently and are a bug.
|
|
330
|
+
|
|
331
|
+
### Step 6 — Validate
|
|
332
|
+
|
|
333
|
+
- Confirm every `id` used in code exists in every catalog.
|
|
334
|
+
- Confirm placeholder names match between the source string, the
|
|
335
|
+
`defaultMessage` in code, and every translated value.
|
|
336
|
+
- Confirm rich-text tags (e.g. `<link>...</link>`) are balanced in every locale.
|
|
337
|
+
|
|
338
|
+
## 10. Skeleton for a New Catalog File
|
|
339
|
+
|
|
340
|
+
When creating the catalog from scratch in a new project, start with the
|
|
341
|
+
cross-cutting `shared.*` namespace and grow per feature.
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"shared.buttons.save": "Save",
|
|
346
|
+
"shared.buttons.cancel": "Cancel",
|
|
347
|
+
"shared.buttons.delete": "Delete",
|
|
348
|
+
"shared.buttons.edit": "Edit",
|
|
349
|
+
"shared.buttons.add": "Add",
|
|
350
|
+
"shared.buttons.confirm": "Confirm",
|
|
351
|
+
"shared.buttons.close": "Close",
|
|
352
|
+
"shared.labels.loading": "Loading...",
|
|
353
|
+
"shared.labels.required": "Required",
|
|
354
|
+
"shared.labels.optional": "Optional",
|
|
355
|
+
"shared.labels.noResults": "No results found",
|
|
356
|
+
"shared.errors.somethingWrong": "Something went wrong!",
|
|
357
|
+
"shared.errors.tryAgain": "Try again",
|
|
358
|
+
"shared.pagination.counter": "{items} of {total}",
|
|
359
|
+
"shared.drawer.addTitle": "Add {entityName}",
|
|
360
|
+
"shared.drawer.deleteTitle": "Delete {entityName}",
|
|
361
|
+
"shared.validation.nameRequired": "Name is required"
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Then add feature-specific keys following the same conventions, e.g.:
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"addresses.forms.country": "Country",
|
|
370
|
+
"addresses.forms.street": "Street Address",
|
|
371
|
+
"addresses.validation.streetRequired": "Street Address is required",
|
|
372
|
+
"addresses.components.deleteAddress.title": "Delete address",
|
|
373
|
+
"addresses.components.deleteAddress.toastSuccess": "Address deleted successfully"
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## 11. Quick Checklist for Agents
|
|
378
|
+
|
|
379
|
+
Before opening a PR that touches translations:
|
|
380
|
+
|
|
381
|
+
- [ ] All new ids follow `<feature>.<group>[.<subGroup>].<name>` in camelCase.
|
|
382
|
+
- [ ] Every new id exists in **all** locale files in `src/messages/`.
|
|
383
|
+
- [ ] `defaultMessage` in code matches the value in the source-of-truth locale
|
|
384
|
+
byte-for-byte (including punctuation and trailing periods).
|
|
385
|
+
- [ ] Placeholder names are camelCase and identical across all locales.
|
|
386
|
+
- [ ] Rich-text tags (`<link>`, etc.) are balanced and reuse existing tags
|
|
387
|
+
whenever possible.
|
|
388
|
+
- [ ] No nested objects in the catalog — keys are flat dotted strings.
|
|
389
|
+
- [ ] Generic strings landed under `shared.*` rather than duplicated per
|
|
390
|
+
feature.
|
|
391
|
+
- [ ] No hard-coded user-facing literals remain in the touched components.
|