@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.
Files changed (219) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/cms/b2b/components/.gitkeep +0 -0
  3. package/cms/b2b/pages/cms_content_type__i18n.jsonc +36 -0
  4. package/cypress/integration/localization.test.ts +482 -0
  5. package/cypress/support/commands.ts +8 -0
  6. package/docs/i18n/i18n-messages-format.md +391 -0
  7. package/docs/i18n/locales.json +701 -0
  8. package/docs/i18n/migration-inventory.md +959 -0
  9. package/package.json +3 -3
  10. package/specs/custom-localization-system.md +711 -0
  11. package/specs/multi-locale-e2e-validation.md +277 -0
  12. package/src/__mocks__/discovery.config.ts +12 -0
  13. package/src/features/accounting-fields/components/AccountingConfirmDelete/AccountingConfirmDelete.tsx +14 -4
  14. package/src/features/accounting-fields/components/AccountingFieldConfirmEdit/AccountingFieldConfirmEdit.tsx +11 -6
  15. package/src/features/accounting-fields/components/AccountingFieldDropdown/AccountingFieldDropdown.tsx +5 -3
  16. package/src/features/accounting-fields/components/AccountingFieldForm/AccountingFieldForm.tsx +33 -23
  17. package/src/features/accounting-fields/components/AccountingValueConfirmEdit/AccountingValueConfirmEdit.tsx +10 -6
  18. package/src/features/accounting-fields/components/AccountingValueDropdown/AccountingValueDropdown.tsx +19 -9
  19. package/src/features/accounting-fields/components/AccountingValueForm/AccountingValueForm.tsx +21 -10
  20. package/src/features/accounting-fields/components/CreateAccountingFieldDrawer/CreateAccountingFieldDrawer.tsx +11 -8
  21. package/src/features/accounting-fields/components/CreateAccountingValueDrawer/CreateAccountingValueDrawer.tsx +18 -7
  22. package/src/features/accounting-fields/components/DeleteAccountingFieldDrawer/DeleteAccountingFieldDrawer.tsx +14 -12
  23. package/src/features/accounting-fields/components/DeleteAccountingValueDrawer/DeleteAccountingValueDrawer.tsx +20 -10
  24. package/src/features/accounting-fields/components/EditAccountingFieldDrawer/EditAccountingFieldDrawer.tsx +5 -3
  25. package/src/features/accounting-fields/components/EditAccountingValueDrawer/EditAccountingValueDrawer.tsx +13 -6
  26. package/src/features/accounting-fields/components/RemoveValueFromUnitDrawer/RemoveValueFromUnitDrawer.tsx +19 -8
  27. package/src/features/accounting-fields/layouts/AccountingFieldLayout/AccountingFieldLayout.tsx +11 -9
  28. package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +13 -7
  29. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +22 -20
  30. package/src/features/addresses/components/AddressForm/AddressForm.tsx +33 -22
  31. package/src/features/addresses/components/AddressLine/AddressLine.tsx +29 -25
  32. package/src/features/addresses/components/AddressLocationsList/AddressLocationsList.tsx +15 -6
  33. package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +9 -4
  34. package/src/features/addresses/components/AddressesCard/AddressesCard.tsx +8 -5
  35. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +27 -14
  36. package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +26 -21
  37. package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +20 -16
  38. package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +26 -14
  39. package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +20 -17
  40. package/src/features/addresses/components/EditAddressDrawer/EditAddressDrawer.tsx +12 -6
  41. package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +24 -17
  42. package/src/features/addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx +17 -15
  43. package/src/features/addresses/components/LocationForm/LocationField/LocationField.tsx +8 -5
  44. package/src/features/addresses/components/LocationForm/LocationForm.tsx +9 -3
  45. package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +15 -9
  46. package/src/features/addresses/components/RecipientsForm/RecipientItem/RecipientItem.tsx +55 -46
  47. package/src/features/addresses/components/RecipientsForm/RecipientsForm.tsx +4 -1
  48. package/src/features/addresses/components/RemoveAddressDrawer/RemoveAddressDrawer.tsx +21 -14
  49. package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +16 -9
  50. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +11 -8
  51. package/src/features/b2b-agent/layouts/B2BAgentLayout/B2BAgentLayout.tsx +3 -1
  52. package/src/features/budgets/components/BudgetAddForm/BudgetAddForm.tsx +14 -12
  53. package/src/features/budgets/components/BudgetAddSuccess/BudgetAddSuccess.tsx +16 -14
  54. package/src/features/budgets/components/BudgetAllocationsSelection/BudgetAllocationsSelection.tsx +8 -5
  55. package/src/features/budgets/components/BudgetAllocationsTable/BudgetAllocationsTable.tsx +9 -6
  56. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +23 -17
  57. package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +13 -6
  58. package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +31 -24
  59. package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +15 -9
  60. package/src/features/budgets/components/BudgetRemainingBalance/BudgetRemainingBalance.tsx +6 -2
  61. package/src/features/budgets/components/BudgetSettingsInfo/BudgetSettingsInfo.tsx +27 -10
  62. package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +14 -7
  63. package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +11 -8
  64. package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +8 -5
  65. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +31 -18
  66. package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +17 -12
  67. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +14 -8
  68. package/src/features/budgets/components/EmptyState.tsx/EmptyState.tsx +4 -1
  69. package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +7 -5
  70. package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +5 -3
  71. package/src/features/budgets/messages/budgetNotifications.ts +5 -5
  72. package/src/features/budgets/utils/handleBudgetError.tsx +5 -8
  73. package/src/features/budgets/utils/handleBudgetError.types.ts +3 -0
  74. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingFieldCriteriaSelector.tsx +5 -3
  75. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingValueCriteriaSelector.tsx +20 -9
  76. package/src/features/buying-policies/components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer.tsx +8 -5
  77. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +49 -33
  78. package/src/features/buying-policies/components/BudgetCriteriaSelector/BudgetCriteriaSelector.tsx +8 -2
  79. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +5 -3
  80. package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +28 -24
  81. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +10 -7
  82. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +11 -4
  83. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +26 -9
  84. package/src/features/buying-policies/utils/accountingFieldCriteriaHelpers.ts +2 -2
  85. package/src/features/buying-policies/utils/buyingPoliciesWorkflowTypes.ts +3 -3
  86. package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +8 -8
  87. package/src/features/buying-policies/utils/spendingLimitsCriteriaOptions.ts +6 -6
  88. package/src/features/contracts/components/ContractsCard/ContractsCard.tsx +7 -5
  89. package/src/features/contracts/layouts/ContractsLayout/ContractsLayout.tsx +9 -4
  90. package/src/features/credit-cards/components/CreateCreditCardDrawer/CreateCreditCardDrawer.tsx +17 -11
  91. package/src/features/credit-cards/components/CreditCardDropdownMenu/CreditCardDropdownMenu.tsx +17 -11
  92. package/src/features/credit-cards/components/CreditCardForm/CreditCardForm.tsx +8 -5
  93. package/src/features/credit-cards/components/CreditCardSettingsDrawer/CreditCardSettingsDrawer.tsx +10 -8
  94. package/src/features/credit-cards/components/DeleteCreditCardDrawer/DeleteCreditCardDrawer.tsx +19 -14
  95. package/src/features/credit-cards/components/EditCreditCardDrawer/EditCreditCardDrawer.tsx +14 -13
  96. package/src/features/credit-cards/components/RemoveCreditCardDrawer/RemoveCreditCardDrawer.tsx +18 -15
  97. package/src/features/credit-cards/layouts/CreditCardsLayout/CreditCardLayout.tsx +10 -4
  98. package/src/features/order-entry/layouts/OrderEntryLayout.tsx +3 -1
  99. package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +11 -9
  100. package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +28 -16
  101. package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +18 -12
  102. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +28 -29
  103. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbLink.tsx +4 -4
  104. package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/OrgUnitDetailsDropdownMenu.tsx +4 -2
  105. package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +23 -5
  106. package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +18 -2
  107. package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +6 -4
  108. package/src/features/org-units/components/OrgUnitsHierarchyTree/OrgUnitsHierarchyTree.tsx +9 -6
  109. package/src/features/org-units/components/OrganizationalUnitsCard/OrganizationalUnitsCard.tsx +15 -7
  110. package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +15 -9
  111. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +27 -26
  112. package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +8 -3
  113. package/src/features/payment-methods/components/AddPaymentMethodsDrawer/AddPaymentMethodsDrawer.tsx +29 -18
  114. package/src/features/payment-methods/components/PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer.tsx +13 -9
  115. package/src/features/payment-methods/components/RemoveMethodButton/RemoveMethodButton.tsx +5 -2
  116. package/src/features/payment-methods/components/RemovePaymentMethodsDrawer/RemovePaymentMethodsDrawer.tsx +10 -7
  117. package/src/features/payment-methods/hooks/useRemovePaymentMethodSubmit.ts +6 -4
  118. package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +21 -15
  119. package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +11 -7
  120. package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +9 -6
  121. package/src/features/profile/components/ProfileCard/ProfileCard.tsx +8 -5
  122. package/src/features/profile/components/ProfileSummary/ProfileSummary.tsx +13 -6
  123. package/src/features/profile/layouts/ProfileLayout/ProfileLayout.tsx +16 -6
  124. package/src/features/roles/layout/RoleDetailsLayout/RoleDetailsLayout.tsx +4 -2
  125. package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +5 -3
  126. package/src/features/shared/components/BasicCard/BasicCard.tsx +6 -4
  127. package/src/features/shared/components/BasicCard/BasicCardLine.tsx +5 -4
  128. package/src/features/shared/components/CustomerSwitch/CustomerSwitchDrawer.tsx +10 -2
  129. package/src/features/shared/components/CustomerSwitch/CustomerSwitchSearch.tsx +4 -1
  130. package/src/features/shared/components/DropdownFilter/DropdownFilter.tsx +11 -7
  131. package/src/features/shared/components/Error/Error.tsx +13 -6
  132. package/src/features/shared/components/HeaderInside/HeaderInside.tsx +5 -2
  133. package/src/features/shared/components/InternalSearch/InternalSearch.tsx +4 -1
  134. package/src/features/shared/components/InternalTopbar/InternalTopbar.tsx +7 -5
  135. package/src/features/shared/components/MainLinksDropdownMenu/MainLinksDropdownMenu.tsx +23 -8
  136. package/src/features/shared/components/OrgUnitInputSearch/OrgUnitInputSearch.tsx +7 -2
  137. package/src/features/shared/components/Pagination/Pagination.tsx +6 -2
  138. package/src/features/shared/components/Paginator/Counter.tsx +11 -5
  139. package/src/features/shared/components/Paginator/Interval.tsx +4 -3
  140. package/src/features/shared/components/Paginator/NextPageButton.tsx +6 -2
  141. package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +6 -4
  142. package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +11 -10
  143. package/src/features/shared/components/SettingsDrawer/listTypeOptions.ts +10 -7
  144. package/src/features/shared/components/SortFilter/SortFilter.tsx +15 -8
  145. package/src/features/shared/components/Table/TableRow/TableRow.tsx +5 -1
  146. package/src/features/shared/components/VerticalNav/VerticalNavLink.tsx +7 -3
  147. package/src/features/shared/layouts/BaseTabsLayout/Navbar.tsx +28 -4
  148. package/src/features/shared/layouts/BaseTabsLayout/about-drawer/AboutDrawer.tsx +15 -5
  149. package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +24 -9
  150. package/src/features/shared/layouts/BaseTabsLayout/sidebar-drawer/SidebarDrawer.tsx +5 -2
  151. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +10 -5
  152. package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +7 -4
  153. package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +9 -4
  154. package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +7 -4
  155. package/src/features/shared/layouts/OrgUnitTabsLayout/OrgUnitTabLayout.tsx +3 -0
  156. package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +13 -7
  157. package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +9 -5
  158. package/src/features/shared/localization/LocalizationContext.tsx +35 -0
  159. package/src/features/shared/localization/__tests__/createLocalize.test.ts +166 -0
  160. package/src/features/shared/localization/__tests__/get-localization-labels.service.test.ts +202 -0
  161. package/src/features/shared/localization/clients/LocalizationClient.ts +19 -0
  162. package/src/features/shared/localization/constants.ts +31 -0
  163. package/src/features/shared/localization/createLocalize.ts +78 -0
  164. package/src/features/shared/localization/services/get-localization-labels.service.ts +76 -0
  165. package/src/features/shared/localization/types.ts +66 -0
  166. package/src/features/shared/localization/withLocalization.tsx +22 -0
  167. package/src/features/shared/types/AuthRouteProps.ts +5 -0
  168. package/src/features/shared/types/LoaderData.ts +1 -0
  169. package/src/features/shared/utils/addresLabelToPropMapping.ts +10 -10
  170. package/src/features/shared/utils/constants.ts +1 -4
  171. package/src/features/shared/utils/getContractSettingsLinks.ts +17 -12
  172. package/src/features/shared/utils/getFinanceSettingsLinks.ts +14 -9
  173. package/src/features/shared/utils/getKeyByValue.ts +2 -2
  174. package/src/features/shared/utils/getOrganizationSettingsLinks.ts +13 -3
  175. package/src/features/shared/utils/getSumaListingSidebarLinks.ts +15 -10
  176. package/src/features/shared/utils/index.ts +2 -0
  177. package/src/features/shared/utils/routeLayoutMapping.ts +30 -30
  178. package/src/features/shared/utils/search.tsx +10 -0
  179. package/src/features/shared/utils/withAuth.tsx +6 -0
  180. package/src/features/shared/utils/withAuthLoader.ts +11 -0
  181. package/src/features/shared/utils/withLoaderErrorBoundary.ts +9 -0
  182. package/src/features/shared/utils/withProviders.tsx +13 -2
  183. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +39 -27
  184. package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +41 -39
  185. package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +27 -23
  186. package/src/features/users/components/EmailTransactionalSection/EmailTransactionalSection.tsx +6 -8
  187. package/src/features/users/components/LoginField/LoginField.tsx +4 -4
  188. package/src/features/users/components/ReassignOrgUnitDrawer/ReassignOrgUnitDrawer.tsx +22 -19
  189. package/src/features/users/components/ResetPasswordDrawer/ResetPasswordDrawer.tsx +16 -21
  190. package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +26 -15
  191. package/src/features/users/components/UpdateUserDrawerWithUsername/UpdateUserDrawerWithUsername.tsx +22 -8
  192. package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +7 -5
  193. package/src/features/users/components/UserFormFields/UserFormFields.tsx +9 -8
  194. package/src/features/users/components/UsersCard/UsersCard.tsx +4 -2
  195. package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +26 -10
  196. package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +12 -8
  197. package/src/pages/accounting-field.tsx +28 -15
  198. package/src/pages/address-details.tsx +37 -26
  199. package/src/pages/addresses.tsx +27 -12
  200. package/src/pages/budgets-details.tsx +25 -15
  201. package/src/pages/budgets.tsx +31 -17
  202. package/src/pages/buyer-organization-manager.tsx +23 -12
  203. package/src/pages/buying-policies.tsx +38 -20
  204. package/src/pages/buying-policy-details.tsx +20 -19
  205. package/src/pages/contracts.tsx +15 -1
  206. package/src/pages/credit-cards.tsx +30 -17
  207. package/src/pages/order-entry.tsx +26 -15
  208. package/src/pages/org-unit-details.tsx +20 -6
  209. package/src/pages/org-units.tsx +20 -6
  210. package/src/pages/payment-groups.tsx +15 -12
  211. package/src/pages/productAssortment.tsx +10 -1
  212. package/src/pages/profile.tsx +14 -1
  213. package/src/pages/role-details.tsx +19 -6
  214. package/src/pages/roles.tsx +27 -14
  215. package/src/pages/user-details.tsx +29 -17
  216. package/src/pages/users.tsx +11 -0
  217. package/vitest.config.ts +13 -0
  218. package/src/features/addresses/constants/messages.ts +0 -8
  219. 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.