Pārlūkot izejas kodu

feat(dashboard): Full localization for 25 languages (#3847)

Michael Bromley 3 mēneši atpakaļ
vecāks
revīzija
6c85b28e92
100 mainītis faili ar 1181 papildinājumiem un 584 dzēšanām
  1. 3 3
      .cursor/rules/dashboard.mdc
  2. 88 20
      package-lock.json
  3. 4 0
      packages/dashboard/README.md
  4. 25 2
      packages/dashboard/lingui.config.js
  5. 159 154
      packages/dashboard/package.json
  6. 61 0
      packages/dashboard/scripts/translate/README.md
  7. 376 0
      packages/dashboard/scripts/translate/i18n-tool.js
  8. 6 5
      packages/dashboard/src/app/common/delete-bulk-action.tsx
  9. 4 5
      packages/dashboard/src/app/common/duplicate-bulk-action.tsx
  10. 1 1
      packages/dashboard/src/app/common/duplicate-entity-dialog.tsx
  11. 7 0
      packages/dashboard/src/app/common/set-document-direction.ts
  12. 21 3
      packages/dashboard/src/app/main.tsx
  13. 6 4
      packages/dashboard/src/app/routes/_authenticated/_administrators/administrators.tsx
  14. 17 6
      packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx
  15. 2 2
      packages/dashboard/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx
  16. 1 1
      packages/dashboard/src/app/routes/_authenticated/_assets/assets.tsx
  17. 4 4
      packages/dashboard/src/app/routes/_authenticated/_assets/assets_.$id.tsx
  18. 8 6
      packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-bulk-actions.tsx
  19. 1 1
      packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx
  20. 1 1
      packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx
  21. 3 8
      packages/dashboard/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx
  22. 3 6
      packages/dashboard/src/app/routes/_authenticated/_channels/channels.tsx
  23. 5 5
      packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx
  24. 3 4
      packages/dashboard/src/app/routes/_authenticated/_collections/collections.tsx
  25. 4 6
      packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx
  26. 1 1
      packages/dashboard/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx
  27. 1 1
      packages/dashboard/src/app/routes/_authenticated/_collections/components/collection-contents-sheet.tsx
  28. 6 6
      packages/dashboard/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx
  29. 2 3
      packages/dashboard/src/app/routes/_authenticated/_countries/countries.tsx
  30. 4 4
      packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx
  31. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-sheet.tsx
  32. 4 4
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx
  33. 2 4
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx
  34. 13 6
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx
  35. 8 8
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx
  36. 3 3
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-address-form.tsx
  37. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx
  38. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx
  39. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx
  40. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/use-customer-history.ts
  41. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-status-badge.tsx
  42. 5 4
      packages/dashboard/src/app/routes/_authenticated/_customers/customers.tsx
  43. 10 8
      packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx
  44. 1 1
      packages/dashboard/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx
  45. 6 5
      packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx
  46. 1 1
      packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx
  47. 1 1
      packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx
  48. 5 5
      packages/dashboard/src/app/routes/_authenticated/_facets/facets.tsx
  49. 7 5
      packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx
  50. 4 4
      packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx
  51. 5 5
      packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx
  52. 19 21
      packages/dashboard/src/app/routes/_authenticated/_orders/components/add-manual-payment-dialog.tsx
  53. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx
  54. 22 22
      packages/dashboard/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx
  55. 6 6
      packages/dashboard/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx
  56. 15 9
      packages/dashboard/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx
  57. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-address.tsx
  58. 11 9
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx
  59. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx
  60. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx
  61. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx
  62. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/use-order-history.ts
  63. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx
  64. 4 4
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx
  65. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx
  66. 27 27
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx
  67. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-table.tsx
  68. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/order-tax-summary.tsx
  69. 26 20
      packages/dashboard/src/app/routes/_authenticated/_orders/components/payment-details.tsx
  70. 3 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx
  71. 6 6
      packages/dashboard/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx
  72. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx
  73. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx
  74. 3 2
      packages/dashboard/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx
  75. 5 9
      packages/dashboard/src/app/routes/_authenticated/_orders/orders.tsx
  76. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx
  77. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx
  78. 4 4
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx
  79. 17 17
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx
  80. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx
  81. 5 6
      packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx
  82. 13 6
      packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx
  83. 1 1
      packages/dashboard/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx
  84. 1 1
      packages/dashboard/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx
  85. 1 2
      packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants.tsx
  86. 13 6
      packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx
  87. 5 5
      packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx
  88. 5 5
      packages/dashboard/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx
  89. 5 4
      packages/dashboard/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx
  90. 9 12
      packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx
  91. 1 1
      packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx
  92. 4 4
      packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants.tsx
  93. 1 1
      packages/dashboard/src/app/routes/_authenticated/_products/components/option-groups-editor.tsx
  94. 1 1
      packages/dashboard/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx
  95. 3 3
      packages/dashboard/src/app/routes/_authenticated/_products/components/product-option-select.tsx
  96. 1 1
      packages/dashboard/src/app/routes/_authenticated/_products/components/product-options-table.tsx
  97. 1 0
      packages/dashboard/src/app/routes/_authenticated/_products/products.graphql.ts
  98. 5 6
      packages/dashboard/src/app/routes/_authenticated/_products/products.tsx
  99. 4 4
      packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx
  100. 11 11
      packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx

+ 3 - 3
.cursor/rules/dashboard.mdc

@@ -82,7 +82,7 @@ export function MyComponent() {
     });
 
 
-   // ... 
+   // ...
 
    function handleClick() {
         // variables are passed here
@@ -145,7 +145,7 @@ Please prefer this form over the raw Shadcn components where possible.
 Any labels or user-facing messages should use localization following this example:
 
 ```tsx
-import { Trans, useLingui } from '@/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 
 
 export function MyComponent() {
@@ -170,4 +170,4 @@ React components should have their props objects set to `Readonly<>`, for instan
 export function PaymentDetails({ payment, currencyCode }: Readonly<PaymentDetailsProps>) {
   //...
 }
-```
+```

+ 88 - 20
package-lock.json

@@ -10697,6 +10697,23 @@
         }
       }
     },
+    "node_modules/@lingui/swc-plugin": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmjs.org/@lingui/swc-plugin/-/swc-plugin-5.6.1.tgz",
+      "integrity": "sha512-kT/ghCKMlTa+SJZU/xn2vvU1QE3/NO3m3Feg6r2OVOovAB6VHKShVElU5truBC2KXn/cPqE9Kz2Yj0+jUmO6xQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@lingui/core": "5"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "next": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@lingui/vite-plugin": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/@lingui/vite-plugin/-/vite-plugin-5.5.0.tgz",
@@ -19354,7 +19371,6 @@
       "version": "1.13.5",
       "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
       "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==",
-      "devOptional": true,
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -19412,7 +19428,6 @@
       "cpu": [
         "x64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19429,7 +19444,6 @@
       "cpu": [
         "arm"
       ],
-      "dev": true,
       "license": "Apache-2.0",
       "optional": true,
       "os": [
@@ -19446,7 +19460,6 @@
       "cpu": [
         "arm64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19463,7 +19476,6 @@
       "cpu": [
         "arm64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19496,7 +19508,6 @@
       "cpu": [
         "x64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19513,7 +19524,6 @@
       "cpu": [
         "arm64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19530,7 +19540,6 @@
       "cpu": [
         "ia32"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19547,7 +19556,6 @@
       "cpu": [
         "x64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19564,7 +19572,6 @@
       "cpu": [
         "arm64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19581,7 +19588,6 @@
       "cpu": [
         "x64"
       ],
-      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -19595,14 +19601,13 @@
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
       "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
-      "devOptional": true,
       "license": "Apache-2.0"
     },
     "node_modules/@swc/helpers": {
       "version": "0.5.17",
       "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
       "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
-      "dev": true,
+      "devOptional": true,
       "license": "Apache-2.0",
       "dependencies": {
         "tslib": "^2.8.0"
@@ -19612,7 +19617,6 @@
       "version": "0.1.25",
       "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
       "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
-      "devOptional": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@swc/counter": "^0.1.3"
@@ -22056,7 +22060,6 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
       "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/@types/pg": {
@@ -22809,6 +22812,28 @@
         "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
       }
     },
+    "node_modules/@vitejs/plugin-react-swc": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.1.0.tgz",
+      "integrity": "sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==",
+      "license": "MIT",
+      "dependencies": {
+        "@rolldown/pluginutils": "1.0.0-beta.35",
+        "@swc/core": "^1.13.5"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "vite": "^4 || ^5 || ^6 || ^7"
+      }
+    },
+    "node_modules/@vitejs/plugin-react-swc/node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.35",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz",
+      "integrity": "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==",
+      "license": "MIT"
+    },
     "node_modules/@vitejs/plugin-react/node_modules/@babel/core": {
       "version": "7.28.4",
       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
@@ -24354,6 +24379,46 @@
         "webpack": ">=5"
       }
     },
+    "node_modules/babel-plugin-macros": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+      "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "cosmiconfig": "^7.0.0",
+        "resolve": "^1.19.0"
+      },
+      "engines": {
+        "node": ">=10",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/parse-json": "^4.0.0",
+        "import-fresh": "^3.2.1",
+        "parse-json": "^5.0.0",
+        "path-type": "^4.0.0",
+        "yaml": "^1.10.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/babel-plugin-macros/node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/babel-plugin-polyfill-corejs2": {
       "version": "0.4.14",
       "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
@@ -52760,11 +52825,12 @@
         "@dnd-kit/modifiers": "^9.0.0",
         "@dnd-kit/sortable": "^10.0.0",
         "@hookform/resolvers": "^4.1.3",
-        "@lingui/babel-plugin-lingui-macro": "^5.2.0",
-        "@lingui/cli": "^5.2.0",
-        "@lingui/core": "^5.2.0",
-        "@lingui/react": "^5.2.0",
-        "@lingui/vite-plugin": "^5.2.0",
+        "@lingui/babel-plugin-lingui-macro": "^5.5.0",
+        "@lingui/cli": "^5.5.0",
+        "@lingui/core": "^5.5.0",
+        "@lingui/react": "^5.5.0",
+        "@lingui/swc-plugin": "^5.6.1",
+        "@lingui/vite-plugin": "^5.5.0",
         "@radix-ui/react-accordion": "^1.2.11",
         "@radix-ui/react-alert-dialog": "^1.1.14",
         "@radix-ui/react-aspect-ratio": "^1.1.7",
@@ -52812,9 +52878,11 @@
         "@vendure/common": "3.4.2",
         "@vendure/core": "3.4.2",
         "@vitejs/plugin-react": "^4.3.4",
+        "@vitejs/plugin-react-swc": "^4.1.0",
         "acorn": "^8.11.3",
         "acorn-walk": "^8.3.2",
         "awesome-graphql-client": "^2.1.0",
+        "babel-plugin-macros": "^3.1.0",
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
         "cmdk": "^1.1.1",

+ 4 - 0
packages/dashboard/README.md

@@ -110,3 +110,7 @@ when developing dashboard extensions.
 ## Testing
 
 Run `npm run test` to run tests once, or `npx vitest` to run tests in watch mode
+
+## Translations
+
+See [./scripts/translate/README.md](./scripts/translate/README.md)

+ 25 - 2
packages/dashboard/lingui.config.js

@@ -2,11 +2,34 @@ import { defineConfig } from '@lingui/cli';
 
 export default defineConfig({
     sourceLocale: 'en',
-    locales: ['de', 'en'],
+    locales: [
+        'he',
+        'ar',
+        'de',
+        'en',
+        'es',
+        'pl',
+        'zh_Hans',
+        'zh_Hant',
+        'pt_BR',
+        'pt_PT',
+        'cs',
+        'fr',
+        'ru',
+        'uk',
+        'it',
+        'fa',
+        'ne',
+        'hr',
+        'nb',
+        'sv',
+        'tr',
+        'ja',
+    ],
     catalogs: [
         {
             path: '<rootDir>/src/i18n/locales/{locale}',
-            include: ['<rootDir>'],
+            include: ['<rootDir>/src'],
         },
     ],
 });

+ 159 - 154
packages/dashboard/package.json

@@ -1,159 +1,164 @@
 {
-    "name": "@vendure/dashboard",
-    "private": false,
-    "version": "3.4.2",
-    "type": "module",
-    "repository": {
-        "type": "git",
-        "url": "https://github.com/vendure-ecommerce/vendure"
+  "name": "@vendure/dashboard",
+  "private": false,
+  "version": "3.4.2",
+  "type": "module",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/vendure-ecommerce/vendure"
+  },
+  "homepage": "https://www.vendure.io",
+  "funding": "https://github.com/sponsors/michaelbromley",
+  "publishConfig": {
+    "access": "public"
+  },
+  "scripts": {
+    "dev": "vite",
+    "build:standalone": "vite build",
+    "build:vite": "tsc --project tsconfig.vite.json",
+    "build:plugin": "tsc --project tsconfig.plugin.json && node scripts/build-plugin.js",
+    "build": "npm run build:vite && npm run build:plugin",
+    "watch": "tsc --project tsconfig.vite.json --watch",
+    "test": "vitest run",
+    "lint": "eslint .",
+    "preview": "vite preview",
+    "generate-index": "node scripts/generate-index.js",
+    "i18n:extract": "lingui extract && node ./scripts/translate/i18n-tool.js extract",
+    "i18n:apply": "node ./scripts/translate/i18n-tool.js apply"
+  },
+  "module": "./src/lib/index.ts",
+  "main": "./src/lib/index.ts",
+  "types": "./src/lib/index.d.ts",
+  "exports": {
+    ".": {
+      "types": "./src/lib/index.d.ts",
+      "import": "./src/lib/index.ts",
+      "require": "./src/lib/index.ts"
     },
-    "homepage": "https://www.vendure.io",
-    "funding": "https://github.com/sponsors/michaelbromley",
-    "publishConfig": {
-        "access": "public"
+    "./plugin": {
+      "types": "./dist/plugin/index.d.ts",
+      "import": "./dist/plugin/index.js",
+      "require": "./dist/plugin/index.js"
     },
-    "scripts": {
-        "dev": "vite",
-        "build:standalone": "vite build",
-        "build:vite": "tsc --project tsconfig.vite.json",
-        "build:plugin": "tsc --project tsconfig.plugin.json && node scripts/build-plugin.js",
-        "build": "npm run build:vite && npm run build:plugin",
-        "watch": "tsc --project tsconfig.vite.json --watch",
-        "test": "vitest run",
-        "lint": "eslint .",
-        "preview": "vite preview",
-        "generate-index": "node scripts/generate-index.js"
-    },
-    "module": "./src/lib/index.ts",
-    "main": "./src/lib/index.ts",
-    "types": "./src/lib/index.d.ts",
-    "exports": {
-        ".": {
-            "types": "./src/lib/index.d.ts",
-            "import": "./src/lib/index.ts",
-            "require": "./src/lib/index.ts"
-        },
-        "./plugin": {
-            "types": "./dist/plugin/index.d.ts",
-            "import": "./dist/plugin/index.js",
-            "require": "./dist/plugin/index.js"
-        },
-        "./vite": {
-            "types": "./dist/vite/index.d.ts",
-            "import": "./dist/vite/index.js",
-            "require": "./dist/vite/index.js"
-        }
-    },
-    "files": [
-        "dist",
-        "src",
-        "lingui.config.js",
-        "index.html"
-    ],
-    "dependencies": {
-        "@dnd-kit/core": "^6.3.1",
-        "@dnd-kit/modifiers": "^9.0.0",
-        "@dnd-kit/sortable": "^10.0.0",
-        "@hookform/resolvers": "^4.1.3",
-        "@lingui/babel-plugin-lingui-macro": "^5.2.0",
-        "@lingui/cli": "^5.2.0",
-        "@lingui/core": "^5.2.0",
-        "@lingui/react": "^5.2.0",
-        "@lingui/vite-plugin": "^5.2.0",
-        "@radix-ui/react-accordion": "^1.2.11",
-        "@radix-ui/react-alert-dialog": "^1.1.14",
-        "@radix-ui/react-aspect-ratio": "^1.1.7",
-        "@radix-ui/react-avatar": "^1.1.10",
-        "@radix-ui/react-checkbox": "^1.3.2",
-        "@radix-ui/react-collapsible": "^1.1.11",
-        "@radix-ui/react-context-menu": "^2.2.15",
-        "@radix-ui/react-dialog": "^1.1.14",
-        "@radix-ui/react-dropdown-menu": "^2.1.15",
-        "@radix-ui/react-hover-card": "^1.1.14",
-        "@radix-ui/react-label": "^2.1.7",
-        "@radix-ui/react-menubar": "^1.1.15",
-        "@radix-ui/react-navigation-menu": "^1.2.13",
-        "@radix-ui/react-popover": "^1.1.14",
-        "@radix-ui/react-progress": "^1.1.7",
-        "@radix-ui/react-radio-group": "^1.3.7",
-        "@radix-ui/react-scroll-area": "^1.2.9",
-        "@radix-ui/react-select": "^2.2.5",
-        "@radix-ui/react-separator": "^1.1.7",
-        "@radix-ui/react-slider": "^1.3.5",
-        "@radix-ui/react-slot": "^1.2.3",
-        "@radix-ui/react-switch": "^1.2.5",
-        "@radix-ui/react-tabs": "^1.1.12",
-        "@radix-ui/react-toggle": "^1.1.9",
-        "@radix-ui/react-toggle-group": "^1.1.10",
-        "@radix-ui/react-tooltip": "^1.2.7",
-        "@tailwindcss/vite": "^4.1.5",
-        "@tanstack/eslint-plugin-query": "^5.66.1",
-        "@tanstack/react-query": "^5.66.7",
-        "@tanstack/react-query-devtools": "^5.68.0",
-        "@tanstack/react-router": "^1.105.0",
-        "@tanstack/react-table": "^8.21.2",
-        "@tanstack/router-devtools": "^1.105.0",
-        "@tanstack/router-plugin": "^1.105.0",
-        "@tiptap/extension-floating-menu": "^3.4.4",
-        "@tiptap/extension-image": "^3.4.4",
-        "@tiptap/extension-table": "^3.4.4",
-        "@tiptap/extension-text-style": "^3.4.4",
-        "@tiptap/pm": "^3.4.4",
-        "@tiptap/react": "^3.4.4",
-        "@tiptap/starter-kit": "^3.4.4",
-        "@types/react": "^19.0.10",
-        "@types/react-dom": "^19.0.4",
-        "@uidotdev/usehooks": "^2.4.1",
-        "@vendure/common": "3.4.2",
-        "@vendure/core": "3.4.2",
-        "@vitejs/plugin-react": "^4.3.4",
-        "acorn": "^8.11.3",
-        "acorn-walk": "^8.3.2",
-        "awesome-graphql-client": "^2.1.0",
-        "class-variance-authority": "^0.7.1",
-        "clsx": "^2.1.1",
-        "cmdk": "^1.1.1",
-        "date-fns": "^4.0.0",
-        "embla-carousel-react": "^8.6.0",
-        "express-rate-limit": "^7.5.0",
-        "fast-glob": "^3.3.2",
-        "fs-extra": "^11.2.0",
-        "gql.tada": "^1.8.10",
-        "graphql": "^16.10.0",
-        "input-otp": "^1.4.2",
-        "json-edit-react": "^1.23.1",
-        "lucide-react": "^0.475.0",
-        "motion": "^12.6.2",
-        "next-themes": "^0.4.6",
-        "react": "^19.0.0",
-        "react-day-picker": "^9.8.0",
-        "react-dom": "^19.0.0",
-        "react-dropzone": "^14.3.8",
-        "react-hook-form": "^7.60.0",
-        "react-resizable-panels": "^3.0.3",
-        "recharts": "^2.15.4",
-        "sonner": "^2.0.6",
-        "tailwind-merge": "^3.2.0",
-        "tailwindcss": "^4.1.5",
-        "tailwindcss-animate": "^1.0.7",
-        "tsconfig-paths": "^4.2.0",
-        "tw-animate-css": "^1.2.9",
-        "vaul": "^1.1.2",
-        "vite": "^6.3.5",
-        "zod": "^3.25.76"
-    },
-    "devDependencies": {
-        "@eslint/js": "^9.19.0",
-        "@types/node": "^22.13.4",
-        "eslint": "^9.19.0",
-        "eslint-plugin-react": "^7.37.4",
-        "eslint-plugin-react-hooks": "^5.0.0",
-        "eslint-plugin-react-refresh": "^0.4.18",
-        "globals": "^15.14.0",
-        "vite-plugin-dts": "^4.5.3"
-    },
-    "optionalDependencies": {
-        "lightningcss-linux-arm64-musl": "^1.29.3",
-        "lightningcss-linux-x64-musl": "^1.29.1"
+    "./vite": {
+      "types": "./dist/vite/index.d.ts",
+      "import": "./dist/vite/index.js",
+      "require": "./dist/vite/index.js"
     }
+  },
+  "files": [
+    "dist",
+    "src",
+    "lingui.config.js",
+    "index.html"
+  ],
+  "dependencies": {
+    "@dnd-kit/core": "^6.3.1",
+    "@dnd-kit/modifiers": "^9.0.0",
+    "@dnd-kit/sortable": "^10.0.0",
+    "@hookform/resolvers": "^4.1.3",
+    "@lingui/babel-plugin-lingui-macro": "^5.5.0",
+    "@lingui/cli": "^5.5.0",
+    "@lingui/core": "^5.5.0",
+    "@lingui/react": "^5.5.0",
+    "@lingui/swc-plugin": "^5.6.1",
+    "@lingui/vite-plugin": "^5.5.0",
+    "@radix-ui/react-accordion": "^1.2.11",
+    "@radix-ui/react-alert-dialog": "^1.1.14",
+    "@radix-ui/react-aspect-ratio": "^1.1.7",
+    "@radix-ui/react-avatar": "^1.1.10",
+    "@radix-ui/react-checkbox": "^1.3.2",
+    "@radix-ui/react-collapsible": "^1.1.11",
+    "@radix-ui/react-context-menu": "^2.2.15",
+    "@radix-ui/react-dialog": "^1.1.14",
+    "@radix-ui/react-dropdown-menu": "^2.1.15",
+    "@radix-ui/react-hover-card": "^1.1.14",
+    "@radix-ui/react-label": "^2.1.7",
+    "@radix-ui/react-menubar": "^1.1.15",
+    "@radix-ui/react-navigation-menu": "^1.2.13",
+    "@radix-ui/react-popover": "^1.1.14",
+    "@radix-ui/react-progress": "^1.1.7",
+    "@radix-ui/react-radio-group": "^1.3.7",
+    "@radix-ui/react-scroll-area": "^1.2.9",
+    "@radix-ui/react-select": "^2.2.5",
+    "@radix-ui/react-separator": "^1.1.7",
+    "@radix-ui/react-slider": "^1.3.5",
+    "@radix-ui/react-slot": "^1.2.3",
+    "@radix-ui/react-switch": "^1.2.5",
+    "@radix-ui/react-tabs": "^1.1.12",
+    "@radix-ui/react-toggle": "^1.1.9",
+    "@radix-ui/react-toggle-group": "^1.1.10",
+    "@radix-ui/react-tooltip": "^1.2.7",
+    "@tailwindcss/vite": "^4.1.5",
+    "@tanstack/eslint-plugin-query": "^5.66.1",
+    "@tanstack/react-query": "^5.66.7",
+    "@tanstack/react-query-devtools": "^5.68.0",
+    "@tanstack/react-router": "^1.105.0",
+    "@tanstack/react-table": "^8.21.2",
+    "@tanstack/router-devtools": "^1.105.0",
+    "@tanstack/router-plugin": "^1.105.0",
+    "@tiptap/extension-floating-menu": "^3.4.4",
+    "@tiptap/extension-image": "^3.4.4",
+    "@tiptap/extension-table": "^3.4.4",
+    "@tiptap/extension-text-style": "^3.4.4",
+    "@tiptap/pm": "^3.4.4",
+    "@tiptap/react": "^3.4.4",
+    "@tiptap/starter-kit": "^3.4.4",
+    "@types/react": "^19.0.10",
+    "@types/react-dom": "^19.0.4",
+    "@uidotdev/usehooks": "^2.4.1",
+    "@vendure/common": "3.4.2",
+    "@vendure/core": "3.4.2",
+    "@vitejs/plugin-react": "^4.3.4",
+    "@vitejs/plugin-react-swc": "^4.1.0",
+    "acorn": "^8.11.3",
+    "acorn-walk": "^8.3.2",
+    "awesome-graphql-client": "^2.1.0",
+    "babel-plugin-macros": "^3.1.0",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "cmdk": "^1.1.1",
+    "date-fns": "^4.0.0",
+    "embla-carousel-react": "^8.6.0",
+    "express-rate-limit": "^7.5.0",
+    "fast-glob": "^3.3.2",
+    "fs-extra": "^11.2.0",
+    "gql.tada": "^1.8.10",
+    "graphql": "^16.10.0",
+    "input-otp": "^1.4.2",
+    "json-edit-react": "^1.23.1",
+    "lucide-react": "^0.475.0",
+    "motion": "^12.6.2",
+    "next-themes": "^0.4.6",
+    "react": "^19.0.0",
+    "react-day-picker": "^9.8.0",
+    "react-dom": "^19.0.0",
+    "react-dropzone": "^14.3.8",
+    "react-hook-form": "^7.60.0",
+    "react-resizable-panels": "^3.0.3",
+    "recharts": "^2.15.4",
+    "sonner": "^2.0.6",
+    "tailwind-merge": "^3.2.0",
+    "tailwindcss": "^4.1.5",
+    "tailwindcss-animate": "^1.0.7",
+    "tsconfig-paths": "^4.2.0",
+    "tw-animate-css": "^1.2.9",
+    "vaul": "^1.1.2",
+    "vite": "^6.3.5",
+    "zod": "^3.25.76"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.19.0",
+    "@types/node": "^22.13.4",
+    "eslint": "^9.19.0",
+    "eslint-plugin-react": "^7.37.4",
+    "eslint-plugin-react-hooks": "^5.0.0",
+    "eslint-plugin-react-refresh": "^0.4.18",
+    "globals": "^15.14.0",
+    "vite-plugin-dts": "^4.5.3"
+  },
+  "optionalDependencies": {
+    "lightningcss-linux-arm64-musl": "^1.29.3",
+    "lightningcss-linux-x64-musl": "^1.29.1"
+  }
 }

+ 61 - 0
packages/dashboard/scripts/translate/README.md

@@ -0,0 +1,61 @@
+# Vendure Dashboard Translation Tooling
+
+The script in this dir allow the efficient addition of new translations to the dashboard.
+
+## Adding a New Language
+
+1. Add the new language code to `packages/dashboard/lingui.config.js`
+2. run `lingui extract`
+
+This will create the new, empty .po file in `packages/dashboard/src/i18n/locales`
+
+## Generating new translations
+
+When you add new localized strings to the Dashboard app, you can generate translations
+in each supported language like this:
+
+1. run `npm run i18n:extract`
+2. this will output a `missing-translations.txt` file in the dashboard dir
+3. paste this into an LLM
+4. save the output into `translations.txt` in the dashboard dir
+5. run `npm run i18n:apply translations.txt`
+
+## Translation Guidelines
+
+### General Principles
+
+1. **Preserve Technical Terms**: Keep terms like "API", "GraphQL", "JSON", "SKU" untranslated
+2. **Maintain Placeholders**: Never modify placeholders like `{0}`, `{formattedDiff}`, `{entityName}`
+3. **Use Formal Address**: Use formal addressing appropriate for business software
+4. **Brand Names**: Keep "Vendure" and other brand names untranslated
+5. **Context Awareness**: Consider the UI context when translating
+
+### Critical Lingui-Specific Rules
+
+1. **Explicit ID Translations**: For entries marked with "js-lingui-explicit-id" (e.g., `orderState.PartiallyDelivered`), translate ONLY the human-readable part, NOT the namespace:
+   - ✅ `"orderState.PartiallyDelivered"` → `"Partiellement livré"` (French)
+   - ❌ `"orderState.PartiallyDelivered"` → `"orderState.Partiellement livré"`
+
+2. **E-commerce Domain Context**: Always assume e-commerce context unless clearly indicated otherwise:
+   - `"order"` = e-commerce order (goods being purchased)
+   - `"customer"` = e-commerce customer (buyer)
+   - `"product"` = e-commerce product (item for sale)
+   - `"variant"` = product variant (size, color, etc.)
+
+## File Structure
+
+```
+packages/dashboard/src/i18n/locales/
+├── en.po          # Source English file
+├── de.po          # German translations
+├── fr.po          # French translations (after creation)
+└── ...            # Other language files
+```
+
+## Tips for Quality Translations
+
+1. **Test in Context**: Always test translations in the actual UI
+2. **Consistency**: Use consistent terminology across the application
+3. **Length Considerations**: Some languages are more verbose - ensure UI still works
+4. **Cultural Adaptation**: Consider cultural differences in business terminology
+5. **Regular Updates**: Keep translations updated when English source changes

+ 376 - 0
packages/dashboard/scripts/translate/i18n-tool.js

@@ -0,0 +1,376 @@
+#!/usr/bin/env node
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Configuration
+const DEFAULT_LOCALES_DIR = '../../src/i18n/locales';
+
+/**
+ * Get all supported languages by scanning .po files in the locales directory
+ */
+function getSupportedLanguages(localesDir) {
+    const files = fs.readdirSync(localesDir);
+    return files
+        .filter(file => file.endsWith('.po'))
+        .map(file => file.slice(0, -3)) // Remove .po extension
+        .filter(lang => lang !== 'en') // Skip English (source language)
+        .sort();
+}
+
+/**
+ * Parse a .po file and extract missing translations (empty msgstr entries)
+ */
+function parsePOFile(filePath) {
+    const content = fs.readFileSync(filePath, 'utf-8');
+    const missingMsgids = [];
+
+    // Split into entries using double newline as separator
+    const entries = content.split(/\n\s*\n/);
+
+    for (const entry of entries) {
+        const lines = entry.trim().split('\n');
+        if (!lines.length) continue;
+
+        const msgidLines = [];
+        const msgstrLines = [];
+        let currentSection = null;
+
+        for (const line of lines) {
+            const trimmedLine = line.trim();
+            if (trimmedLine.startsWith('#')) {
+                continue;
+            } else if (trimmedLine.startsWith('msgid ')) {
+                currentSection = 'msgid';
+                msgidLines.push(trimmedLine.slice(6)); // Remove 'msgid '
+            } else if (trimmedLine.startsWith('msgstr ')) {
+                currentSection = 'msgstr';
+                msgstrLines.push(trimmedLine.slice(7)); // Remove 'msgstr '
+            } else if (trimmedLine.startsWith('"') && currentSection) {
+                if (currentSection === 'msgid') {
+                    msgidLines.push(trimmedLine);
+                } else if (currentSection === 'msgstr') {
+                    msgstrLines.push(trimmedLine);
+                }
+            }
+        }
+
+        // Check if we have a msgid and empty msgstr
+        if (msgidLines.length && msgstrLines.length) {
+            const msgstrContent = msgstrLines.join('');
+            if (msgstrContent === '""' || msgstrContent === '') {
+                // Extract msgid content
+                const msgidContent = msgidLines.join('');
+                if (msgidContent.startsWith('"') && msgidContent.endsWith('"')) {
+                    const msgidText = msgidContent.slice(1, -1); // Remove outer quotes
+                    // Unescape common escape sequences
+                    const unescapedText = msgidText
+                        .replace(/\\"/g, '"')
+                        .replace(/\\n/g, '\n')
+                        .replace(/\\\\/g, '\\');
+
+                    if (unescapedText) {
+                        // Skip empty msgid (header)
+                        missingMsgids.push(unescapedText);
+                    }
+                }
+            }
+        }
+    }
+
+    return missingMsgids;
+}
+
+/**
+ * Extract missing translations from all locale files and generate LLM prompt
+ */
+function extractMissingTranslations(
+    localesDir = DEFAULT_LOCALES_DIR,
+    outputFile = 'missing-translations.txt',
+) {
+    const localesPath = path.resolve(__dirname, localesDir);
+
+    if (!fs.existsSync(localesPath)) {
+        console.error(`Locales directory not found: ${localesPath}`);
+        process.exit(1);
+    }
+
+    console.log(`Extracting missing translations from: ${localesPath}`);
+
+    const missingByLanguage = {};
+    let totalMissing = 0;
+
+    // Get all supported languages from the directory
+    const supportedLanguages = getSupportedLanguages(localesPath);
+    console.log(`Found ${supportedLanguages.length} language files: ${supportedLanguages.join(', ')}`);
+
+    // Process each supported language
+    for (const lang of supportedLanguages) {
+        const poFilePath = path.join(localesPath, `${lang}.po`);
+
+        if (!fs.existsSync(poFilePath)) {
+            console.warn(`Warning: .po file not found for ${lang}`);
+            continue;
+        }
+
+        const missingMsgids = parsePOFile(poFilePath);
+        if (missingMsgids.length > 0) {
+            missingByLanguage[lang] = missingMsgids;
+            totalMissing += missingMsgids.length;
+            console.log(`${lang}: ${missingMsgids.length} missing translations`);
+        } else {
+            console.log(`${lang}: 0 missing translations`);
+        }
+    }
+
+    // Generate LLM prompt with missing translations
+    const promptLines = [
+        '# Translation Request for Vendure Dashboard',
+        '',
+        'Please translate the missing message IDs below for each language. The context is a dashboard for an e-commerce platform called Vendure.',
+        '',
+        '## Instructions:',
+        '1. Translate each msgid into the target language',
+        '2. Maintain the original formatting, including placeholders like {0}, {buttonText}, etc.',
+        '3. Keep HTML tags and markdown formatting intact',
+        '4. Use appropriate UI/technical terminology for each language',
+        '5. Return translations in the exact format: language_code followed by msgid|msgstr pairs',
+        '6. These strings are for use in the Lingui library and use ICU MessageFormat',
+        '7. Always assume e-commerce context unless clearly indicated otherwise',
+        '',
+        '## Expected Output Format:',
+        '```',
+        'language_code',
+        'msgid_text|translated_text',
+        'msgid_text|translated_text',
+        '---',
+        'language_code',
+        'msgid_text|translated_text',
+        '---',
+        '```',
+        '',
+        '## Missing Translations:',
+        '',
+    ];
+
+    // Add missing translations for each language
+    for (const [lang, msgids] of Object.entries(missingByLanguage)) {
+        promptLines.push(lang);
+        for (const msgid of msgids) {
+            promptLines.push(msgid);
+        }
+        promptLines.push('---');
+    }
+
+    // Write to output file
+    const outputPath = path.resolve(outputFile);
+    fs.writeFileSync(outputPath, promptLines.join('\n'), 'utf-8');
+
+    console.log(`\nExtraction completed!`);
+    console.log(`Total missing translations: ${totalMissing}`);
+    console.log(`Languages with missing translations: ${Object.keys(missingByLanguage).length}`);
+    console.log(`Prompt written to: ${outputPath}`);
+    console.log(`\nNext steps:`);
+    console.log(`1. Copy the content of ${outputFile} to Claude or another LLM`);
+    console.log(`2. Save the translated output to a file (e.g., translations.txt)`);
+    console.log(`3. Run: node i18n-tool.js apply <translations-file>`);
+}
+
+/**
+ * Apply translations from LLM output back to .po files
+ */
+function applyTranslations(translationsFile, localesDir = DEFAULT_LOCALES_DIR) {
+    const localesPath = path.resolve(__dirname, localesDir);
+    const translationsPath = path.resolve(translationsFile);
+
+    if (!fs.existsSync(localesPath)) {
+        console.error(`Locales directory not found: ${localesPath}`);
+        process.exit(1);
+    }
+
+    if (!fs.existsSync(translationsPath)) {
+        console.error(`Translations file not found: ${translationsPath}`);
+        process.exit(1);
+    }
+
+    console.log(`Applying translations from: ${translationsPath}`);
+    console.log(`Target directory: ${localesPath}\n`);
+
+    // Read and parse the translations file
+    const translationsContent = fs.readFileSync(translationsPath, 'utf-8');
+    const languageBlocks = translationsContent.split(/\n---\n?/).filter(block => block.trim());
+
+    // Parse translations by language
+    const translationsByLanguage = {};
+
+    languageBlocks.forEach(block => {
+        const lines = block.trim().split('\n');
+        const languageCode = lines[0].trim();
+
+        if (!languageCode) return;
+
+        translationsByLanguage[languageCode] = {};
+
+        // Parse each translation line (format: msgid|msgstr)
+        for (let i = 1; i < lines.length; i++) {
+            const line = lines[i].trim();
+            if (!line) continue;
+
+            const pipeIndex = line.indexOf('|');
+
+            if (pipeIndex === -1) {
+                console.warn(`Warning: Line "${line.substring(0, 50)}..." has no pipe separator, skipping`);
+                continue;
+            }
+
+            const msgid = line.substring(0, pipeIndex);
+            const msgstr = line.substring(pipeIndex + 1);
+
+            translationsByLanguage[languageCode][msgid] = msgstr;
+        }
+    });
+
+    // Apply translations to each language file
+    Object.entries(translationsByLanguage).forEach(([languageCode, translations]) => {
+        const translationCount = Object.keys(translations).length;
+        console.log(`\nProcessing ${languageCode} (${translationCount} translations)...`);
+        updatePoFile(localesPath, languageCode, translations);
+    });
+
+    console.log('\nDone!');
+}
+
+/**
+ * Function to escape special characters in strings for .po files
+ */
+function escapePoString(str) {
+    return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\t/g, '\\t');
+}
+
+/**
+ * Function to escape string for use in regex (including already-escaped quotes)
+ */
+function escapeRegex(str) {
+    // First escape for .po format
+    const poEscaped = escapePoString(str);
+    // Then escape for regex (note: backslashes are already doubled from escapePoString)
+    return poEscaped.replace(/[.*+?^${}()|[\]]/g, '\\$&');
+}
+
+/**
+ * Function to find and update msgstr in .po file
+ */
+function updatePoFile(localesDir, languageCode, translations) {
+    const poFilePath = path.join(localesDir, `${languageCode}.po`);
+
+    if (!fs.existsSync(poFilePath)) {
+        console.warn(`Warning: .po file not found for language ${languageCode}: ${poFilePath}`);
+        return;
+    }
+
+    let poContent = fs.readFileSync(poFilePath, 'utf-8');
+    let updated = 0;
+    let notFound = [];
+
+    // Process each translation
+    Object.entries(translations).forEach(([msgid, msgstr]) => {
+        // Escape the msgid for regex matching
+        const escapedMsgidForRegex = escapeRegex(msgid);
+
+        // Pattern to match msgid with empty msgstr
+        const pattern = new RegExp(`(msgid "${escapedMsgidForRegex}"\\s*\\n)(msgstr "")`, 'gm');
+
+        const matches = poContent.match(pattern);
+
+        if (matches) {
+            poContent = poContent.replace(pattern, `$1msgstr "${escapePoString(msgstr)}"`);
+            updated++;
+        } else {
+            notFound.push(msgid);
+        }
+    });
+
+    // Write updated content back to file
+    if (updated > 0) {
+        fs.writeFileSync(poFilePath, poContent, 'utf-8');
+        console.log(`✓ ${languageCode}: Updated ${updated} translations`);
+    } else {
+        console.log(`- ${languageCode}: No translations updated`);
+    }
+
+    if (notFound.length > 0) {
+        console.log(`  ⚠ ${notFound.length} msgids not found in .po file`);
+        if (notFound.length <= 5) {
+            notFound.forEach(msg => console.log(`    - "${msg}"`));
+        }
+    }
+}
+
+/**
+ * Main CLI interface
+ */
+function main() {
+    const args = process.argv.slice(2);
+    const command = args[0];
+
+    switch (command) {
+        case 'extract':
+            const outputFile = args[1] || 'missing-translations.txt';
+            const localesDir = args[2] || DEFAULT_LOCALES_DIR;
+            extractMissingTranslations(localesDir, outputFile);
+            break;
+
+        case 'apply':
+            if (args.length < 2) {
+                console.error('Usage: node i18n-tool.js apply <translations-file> [locales-dir]');
+                console.error('Example: node i18n-tool.js apply translations.txt');
+                process.exit(1);
+            }
+            const translationsFile = args[1];
+            const targetLocalesDir = args[2] || DEFAULT_LOCALES_DIR;
+            applyTranslations(translationsFile, targetLocalesDir);
+            break;
+
+        default:
+            console.log('Vendure Dashboard i18n Tool');
+            console.log('');
+            console.log('Usage:');
+            console.log('  node i18n-tool.js extract [output-file] [locales-dir]');
+            console.log('    Extract missing translations and generate LLM prompt');
+            console.log('');
+            console.log('  node i18n-tool.js apply <translations-file> [locales-dir]');
+            console.log('    Apply translated strings back to .po files');
+            console.log('');
+            console.log('Examples:');
+            console.log('  node i18n-tool.js extract');
+            console.log('  node i18n-tool.js extract prompt.txt');
+            console.log('  node i18n-tool.js apply translations.txt');
+            console.log('');
+            console.log('Workflow:');
+            console.log('  1. Add new messages to dashboard components');
+            console.log('  2. Run: lingui extract');
+            console.log('  3. Run: node i18n-tool.js extract');
+            console.log('  4. Copy prompt to LLM (Claude, etc.) and get translations');
+            console.log('  5. Save LLM output to a file');
+            console.log('  6. Run: node i18n-tool.js apply <translations-file>');
+            break;
+    }
+}
+
+// Run the CLI if this file is executed directly
+if (import.meta.url === `file://${process.argv[1]}`) {
+    main();
+}
+
+export {
+    applyTranslations,
+    escapePoString,
+    escapeRegex,
+    extractMissingTranslations,
+    parsePOFile,
+    updatePoFile,
+};

+ 6 - 5
packages/dashboard/src/app/common/delete-bulk-action.tsx

@@ -6,7 +6,7 @@ import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-
 import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
 import { getMutationName } from '@/vdb/framework/document-introspection/get-document-structure.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 
 interface DeleteBulkActionProps {
     /** The GraphQL mutation document to execute */
@@ -79,7 +79,7 @@ export function DeleteBulkAction({
     table,
 }: Readonly<DeleteBulkActionProps>) {
     const { refetchPaginatedList } = usePaginatedList();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const queryClient = useQueryClient();
 
     const { mutate } = useMutation({
@@ -105,13 +105,14 @@ export function DeleteBulkAction({
             }
 
             if (0 < deleted) {
-                toast.success(i18n.t(`Deleted ${deleted} ${entityName}`));
+                toast.success(t`Deleted ${deleted} ${entityName}`);
             }
             if (0 < failed) {
+                const allErrors = errors.join(', ');
                 const errorMessage =
                     errors.length > 0
-                        ? i18n.t(`Failed to delete ${failed} ${entityName}: ${errors.join(', ')}`)
-                        : i18n.t(`Failed to delete ${failed} ${entityName}`);
+                        ? t`Failed to delete ${failed} ${entityName}: ${allErrors}`
+                        : t`Failed to delete ${failed} ${entityName}`;
                 toast.error(errorMessage);
             }
 

+ 4 - 5
packages/dashboard/src/app/common/duplicate-bulk-action.tsx

@@ -8,7 +8,7 @@ import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-
 import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
 import { api } from '@/vdb/graphql/api.js';
 import { duplicateEntityDocument } from '@/vdb/graphql/common-operations.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { DuplicateEntityDialog } from './duplicate-entity-dialog.js';
 
 interface DuplicateBulkActionProps {
@@ -31,7 +31,7 @@ export function DuplicateBulkAction({
     table,
 }: Readonly<DuplicateBulkActionProps>) {
     const { refetchPaginatedList } = usePaginatedList();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [isDuplicating, setIsDuplicating] = useState(false);
     const [progress, setProgress] = useState({ completed: 0, total: 0 });
     const [dialogOpen, setDialogOpen] = useState(false);
@@ -93,9 +93,8 @@ export function DuplicateBulkAction({
 
             // Show results
             if (results.success > 0) {
-                toast.success(
-                    i18n.t(`Successfully duplicated ${results.success} ${entityName.toLowerCase()}s`),
-                );
+                const count = results.success;
+                toast.success(t`Successfully duplicated ${count} ${entityName}`);
             }
             if (results.failed > 0) {
                 const errorMessage =

+ 1 - 1
packages/dashboard/src/app/common/duplicate-entity-dialog.tsx

@@ -9,7 +9,7 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { api } from '@/vdb/graphql/api.js';
 import { getEntityDuplicatorsDocument } from '@/vdb/graphql/common-operations.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { useQuery } from '@tanstack/react-query';
 import { ConfigurableOperationInput } from '@vendure/common/lib/generated-types';
 import React, { useState } from 'react';

+ 7 - 0
packages/dashboard/src/app/common/set-document-direction.ts

@@ -0,0 +1,7 @@
+/**
+ * @description
+ * Sets the `dir` attribute on the HTML tag, which sets the global text direction
+ */
+export function setDocumentDirection(direction: 'rtl' | 'ltr') {
+    document.documentElement.setAttribute('dir', direction);
+}

+ 21 - 3
packages/dashboard/src/app/main.tsx

@@ -11,7 +11,12 @@ import { AnyRoute, createRouter, RouterOptions, RouterProvider } from '@tanstack
 import React, { useEffect } from 'react';
 import ReactDOM from 'react-dom/client';
 
+import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
+import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
+import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
+import { DirectionProvider } from '@radix-ui/react-direction';
 import { AppProviders, queryClient } from './app-providers.js';
+import { setDocumentDirection } from './common/set-document-direction.js';
 import { routeTree } from './routeTree.gen.js';
 import './styles.css';
 
@@ -53,7 +58,14 @@ function InnerApp() {
     const auth = useAuth();
     const router = useExtendedRouter(routeTree, routerOptions);
     const serverConfig = useServerConfig();
+    const { isRTL } = useDisplayLocale();
     const [hasSetCustomFieldsMap, setHasSetCustomFieldsMap] = React.useState(false);
+    const { settings } = useUserSettings();
+    const { loadAndActivateLocale } = useUiLanguageLoader();
+
+    useEffect(() => {
+        void loadAndActivateLocale(settings.displayLanguage);
+    }, [settings.displayLanguage]);
 
     useEffect(() => {
         if (!serverConfig) {
@@ -63,11 +75,17 @@ function InnerApp() {
         setHasSetCustomFieldsMap(true);
     }, [serverConfig?.entityCustomFields.length]);
 
+    useEffect(() => {
+        setDocumentDirection(isRTL ? 'rtl' : 'ltr');
+    }, [isRTL]);
+
     return (
         <>
-            {(hasSetCustomFieldsMap || auth.status === 'unauthenticated') && (
-                <RouterProvider router={router} context={{ auth, queryClient }} />
-            )}
+            <DirectionProvider dir={isRTL ? 'rtl' : 'ltr'}>
+                {(hasSetCustomFieldsMap || auth.status === 'unauthenticated') && (
+                    <RouterProvider router={router} context={{ auth, queryClient }} />
+                )}
+            </DirectionProvider>
         </>
     );
 }

+ 6 - 4
packages/dashboard/src/app/routes/_authenticated/_administrators/administrators.tsx

@@ -5,7 +5,7 @@ import { Badge } from '@/vdb/components/ui/badge.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { administratorListDocument } from './administrators.graphql.js';
@@ -20,7 +20,7 @@ function AdministratorListPage() {
     return (
         <ListPage
             pageId="administrator-list"
-            title="Administrators"
+            title={<Trans>Administrators</Trans>}
             listQuery={administratorListDocument}
             route={Route}
             onSearchTermChange={searchTerm => {
@@ -32,7 +32,8 @@ function AdministratorListPage() {
             }}
             additionalColumns={{
                 name: {
-                    header: 'Name',
+                    id: 'name',
+                    header: () => <Trans>Name</Trans>,
                     cell: ({ row }) => (
                         <DetailPageButton
                             id={row.original.id}
@@ -41,7 +42,8 @@ function AdministratorListPage() {
                     ),
                 },
                 roles: {
-                    header: 'Roles',
+                    id: 'roles',
+                    header: () => <Trans>Roles</Trans>,
                     cell: ({ row }) => {
                         return (
                             <div className="flex flex-wrap gap-2">

+ 17 - 6
packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx

@@ -16,7 +16,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import {
@@ -48,7 +48,7 @@ function AdministratorDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -74,16 +74,27 @@ function AdministratorDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast(i18n.t(creatingNewEntity ? 'Successfully created administrator' : 'Successfully updated administrator'));
+            toast(
+                i18n.t(
+                    creatingNewEntity
+                        ? 'Successfully created administrator'
+                        : 'Successfully updated administrator',
+                ),
+            );
             resetForm();
             if (creatingNewEntity) {
                 await navigate({ to: `../$id`, params: { id: data.id } });
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create administrator' : 'Failed to update administrator'), {
-                description: err instanceof Error ? err.message : 'Unknown error',
-            });
+            toast(
+                i18n.t(
+                    creatingNewEntity ? 'Failed to create administrator' : 'Failed to update administrator',
+                ),
+                {
+                    description: err instanceof Error ? err.message : 'Unknown error',
+                },
+            );
         },
     });
 

+ 2 - 2
packages/dashboard/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx

@@ -5,7 +5,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/vdb/
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
 import { useGroupedPermissions } from '@/vdb/hooks/use-grouped-permissions.js';
-import { useLingui } from '@/vdb/lib/trans.js';
+import { useLingui } from '@lingui/react/macro';
 import { useQuery } from '@tanstack/react-query';
 
 const rolesByIdDocument = graphql(`
@@ -29,7 +29,7 @@ interface RolePermissionsDisplayProps {
 }
 
 export function RolePermissionsDisplay({ value = [] }: Readonly<RolePermissionsDisplayProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const groupedPermissions = useGroupedPermissions();
 
     const { data } = useQuery({

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_assets/assets.tsx

@@ -1,6 +1,6 @@
 import { AssetGallery } from '@/vdb/components/shared/asset/asset-gallery.js';
 import { Page, PageBlock, PageTitle } from '@/vdb/framework/layout-engine/page-layout.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute } from '@tanstack/react-router';
 import { DeleteAssetsBulkAction } from './components/asset-bulk-actions.js';
 

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_assets/assets_.$id.tsx

@@ -18,7 +18,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute } from '@tanstack/react-router';
 import { FocusIcon } from 'lucide-react';
 import { useRef, useState } from 'react';
@@ -45,7 +45,7 @@ export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
 
 function AssetDetailPage() {
     const params = Route.useParams();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const imageRef = useRef<HTMLImageElement>(null);
     const [size, setSize] = useState<PreviewPreset>('medium');
@@ -67,11 +67,11 @@ function AssetDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async () => {
-            toast(i18n.t('Successfully updated asset'));
+            toast(t`Successfully updated asset`);
             form.reset(form.getValues());
         },
         onError: err => {
-            toast(i18n.t('Failed to update asset'), {
+            toast(t`Failed to update asset`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 8 - 6
packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-bulk-actions.tsx

@@ -6,7 +6,7 @@ import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-
 import { api } from '@/vdb/graphql/api.js';
 import { AssetFragment } from '@/vdb/graphql/fragments.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { deleteAssetsDocument } from '../assets.graphql.js';
 
 export const DeleteAssetsBulkAction = ({
@@ -16,19 +16,21 @@ export const DeleteAssetsBulkAction = ({
     selection: AssetFragment[];
     refetch: () => void;
 }) => {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
+    const selectionLength = selection.length;
     const { mutate } = useMutation({
         mutationFn: api.mutate(deleteAssetsDocument),
         onSuccess: (result: ResultOf<typeof deleteAssetsDocument>) => {
             if (result.deleteAssets.result === 'DELETED') {
-                toast.success(i18n.t(`Deleted ${selection.length} assets`));
+                toast.success(t`Deleted ${selectionLength} assets`);
             } else {
-                toast.error(i18n.t(`Failed to delete assets: ${result.deleteAssets.message}`));
+                const message = result.deleteAssets.message;
+                toast.error(t`Failed to delete assets: ${message}`);
             }
             refetch();
         },
         onError: () => {
-            toast.error(`Failed to delete ${selection.length} assets`);
+            toast.error(`Failed to delete ${selectionLength} assets`);
         },
     });
 
@@ -37,7 +39,7 @@ export const DeleteAssetsBulkAction = ({
             requiresPermission={['DeleteCatalog', 'DeleteAsset']}
             onClick={() => mutate({ input: { assetIds: selection.map(s => s.id) } })}
             label={<Trans>Delete</Trans>}
-            confirmationText={<Trans>Are you sure you want to delete {selection.length} assets?</Trans>}
+            confirmationText={<Trans>Are you sure you want to delete {selectionLength} assets?</Trans>}
             icon={TrashIcon}
             className="text-destructive"
         />

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx

@@ -10,8 +10,8 @@ import {
 } from '@/vdb/components/ui/command.js';
 import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans } from '@/vdb/lib/trans.js';
 import { cn } from '@/vdb/lib/utils.js';
+import { Trans } from '@lingui/react/macro';
 import { useInfiniteQuery } from '@tanstack/react-query';
 import { useDebounce } from '@uidotdev/usehooks';
 import { Check, Filter, Loader2, X } from 'lucide-react';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx

@@ -10,8 +10,8 @@ import {
 import { Label } from '@/vdb/components/ui/label.js';
 import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans } from '@/vdb/lib/trans.js';
 import { cn } from '@/vdb/lib/utils.js';
+import { Trans } from '@lingui/react/macro';
 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { Check, ChevronsUpDown, Settings2, X } from 'lucide-react';
 import { useCallback, useState } from 'react';

+ 3 - 8
packages/dashboard/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx

@@ -9,8 +9,8 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { Input } from '@/vdb/components/ui/input.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans } from '@/vdb/lib/trans.js';
 import { cn } from '@/vdb/lib/utils.js';
+import { Trans } from '@lingui/react/macro';
 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { Trash2 } from 'lucide-react';
 import { useState } from 'react';
@@ -108,10 +108,7 @@ export function ManageTagsDialog({ open, onOpenChange, onTagsUpdated }: Readonly
             return (
                 <div
                     key={tag.id}
-                    className={cn(
-                        'flex items-center gap-2 p-2 rounded-md',
-                        isDeleted && 'opacity-50',
-                    )}
+                    className={cn('flex items-center gap-2 p-2 rounded-md', isDeleted && 'opacity-50')}
                 >
                     <Input
                         value={getDisplayValue(tag.id)}
@@ -199,9 +196,7 @@ export function ManageTagsDialog({ open, onOpenChange, onTagsUpdated }: Readonly
                     </DialogDescription>
                 </DialogHeader>
 
-                <div className="max-h-[400px] overflow-y-auto space-y-2 py-4">
-                    {renderTagsList()}
-                </div>
+                <div className="max-h-[400px] overflow-y-auto space-y-2 py-4">{renderTagsList()}</div>
 
                 <DialogFooter>
                     <Button variant="outline" onClick={handleCancel} disabled={isSaving}>

+ 3 - 6
packages/dashboard/src/app/routes/_authenticated/_channels/channels.tsx

@@ -5,7 +5,7 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { channelListQuery } from './channels.graphql.js';
@@ -21,7 +21,7 @@ function ChannelListPage() {
     return (
         <ListPage
             pageId="channel-list"
-            title="Channels"
+            title={<Trans>Channels</Trans>}
             listQuery={channelListQuery}
             route={Route}
             defaultVisibility={{
@@ -39,7 +39,6 @@ function ChannelListPage() {
             }}
             customizeColumns={{
                 code: {
-                    header: 'Code',
                     cell: ({ row }) => {
                         return (
                             <DetailPageButton
@@ -50,13 +49,11 @@ function ChannelListPage() {
                     },
                 },
                 seller: {
-                    header: 'Seller',
                     cell: ({ row }) => {
                         return row.original.seller?.name;
                     },
                 },
                 defaultLanguageCode: {
-                    header: 'Default Language',
                     cell: ({ row }) => {
                         return formatLanguageName(row.original.defaultLanguageCode);
                     },
@@ -74,7 +71,7 @@ function ChannelListPage() {
                     <Button asChild>
                         <Link to="./new">
                             <PlusIcon className="mr-2 h-4 w-4" />
-                            New Channel
+                            <Trans>New Channel</Trans>
                         </Link>
                     </Button>
                 </PermissionGuard>

+ 5 - 5
packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx

@@ -23,7 +23,7 @@ import {
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { channelDetailDocument, createChannelDocument, updateChannelDocument } from './channels.graphql.js';
@@ -49,7 +49,7 @@ function ChannelDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const { refreshChannels } = useChannel();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
@@ -82,20 +82,20 @@ function ChannelDetailPage() {
         params: { id: params.id },
         onSuccess: async data => {
             if (data.__typename === 'Channel') {
-                toast(i18n.t(creatingNewEntity ? 'Successfully created channel' : 'Successfully updated channel'));
+                toast(creatingNewEntity ? t`Successfully created channel` : t`Successfully updated channel`);
                 refreshChannels();
                 resetForm();
                 if (creatingNewEntity) {
                     await navigate({ to: `../$id`, params: { id: data.id } });
                 }
             } else {
-                toast(i18n.t(creatingNewEntity ? 'Failed to create channel' : 'Failed to update channel'), {
+                toast(creatingNewEntity ? t`Failed to create channel` : t`Failed to update channel`, {
                     description: data.message,
                 });
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create channel' : 'Failed to update channel'), {
+            toast(creatingNewEntity ? t`Failed to create channel` : t`Failed to update channel`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 3 - 4
packages/dashboard/src/app/routes/_authenticated/_collections/collections.tsx

@@ -4,7 +4,7 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { FetchQueryOptions, useQueries } from '@tanstack/react-query';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { ExpandedState, getExpandedRowModel } from '@tanstack/react-table';
@@ -82,7 +82,7 @@ function CollectionListPage() {
         <>
             <ListPage
                 pageId="collection-list"
-                title="Collections"
+                title={<Trans>Collections</Trans>}
                 listQuery={collectionListDocument}
                 transformVariables={input => {
                     const filterTerm = input.options?.filter?.name?.contains;
@@ -101,7 +101,6 @@ function CollectionListPage() {
                             // in order to correctly render.
                             dependencies: ['children', 'breadcrumbs'],
                         },
-                        header: 'Collection Name',
                         cell: ({ row }) => {
                             const isExpanded = row.getIsExpanded();
                             const hasChildren = !!row.original.children?.length;
@@ -141,7 +140,7 @@ function CollectionListPage() {
                         },
                     },
                     productVariants: {
-                        header: 'Contents',
+                        header: () => <Trans>Contents</Trans>,
                         cell: ({ row }) => {
                             return (
                                 <CollectionContentsSheet

+ 4 - 6
packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx

@@ -22,7 +22,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import {
@@ -53,7 +53,7 @@ function CollectionDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -91,9 +91,7 @@ function CollectionDetailPage() {
         params: { id: params.id },
         onSuccess: async data => {
             toast(
-                i18n.t(
-                    creatingNewEntity ? 'Successfully created collection' : 'Successfully updated collection',
-                ),
+                creatingNewEntity ? t`Successfully created collection` : t`Successfully updated collection`,
             );
             resetForm();
             if (creatingNewEntity) {
@@ -101,7 +99,7 @@ function CollectionDetailPage() {
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create collection' : 'Failed to update collection'), {
+            toast(creatingNewEntity ? t`Failed to create collection` : t`Failed to update collection`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx

@@ -9,7 +9,7 @@ import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from
 import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
 import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
 import {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_collections/components/collection-contents-sheet.tsx

@@ -7,7 +7,7 @@ import {
     SheetTitle,
     SheetTrigger,
 } from '@/vdb/components/ui/sheet.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { PanelLeftOpen } from 'lucide-react';
 import { CollectionContentsTable } from './collection-contents-table.js';
 

+ 6 - 6
packages/dashboard/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx

@@ -16,7 +16,7 @@ import {
 import { Input } from '@/vdb/components/ui/input.js';
 import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { ChevronRight, Folder, FolderOpen, Search } from 'lucide-react';
 
 import { collectionListForMoveDocument, moveCollectionDocument } from '../collections.graphql.js';
@@ -217,7 +217,7 @@ export function MoveCollectionsDialog({
     const debouncedSearchTerm = useDebounce(searchTerm, 300);
     const collectionNameCache = useRef<Map<string, string>>(new Map());
     const queryClient = useQueryClient();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const collectionForMoveKey = ['collectionsForMove', debouncedSearchTerm];
     const childCollectionsForMoveKey = (collectionId?: string) =>
         collectionId ? ['childCollectionsForMove', collectionId] : ['childCollectionsForMove'];
@@ -280,14 +280,14 @@ export function MoveCollectionsDialog({
     const moveCollectionsMutation = useMutation({
         mutationFn: api.mutate(moveCollectionDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Collections moved successfully'));
+            toast.success(t`Collections moved successfully`);
             queryClient.invalidateQueries({ queryKey: collectionForMoveKey });
             queryClient.invalidateQueries({ queryKey: childCollectionsForMoveKey() });
             onSuccess?.();
             onOpenChange(false);
         },
         onError: error => {
-            toast.error(i18n.t('Failed to move collections'));
+            toast.error(t`Failed to move collections`);
             console.error('Move collections error:', error);
         },
     });
@@ -305,7 +305,7 @@ export function MoveCollectionsDialog({
 
     const handleMove = () => {
         if (!selectedCollectionId) {
-            toast.error(i18n.t('Please select a target collection'));
+            toast.error(t`Please select a target collection`);
             return;
         }
         // Move to a specific parent using moveCollection
@@ -363,7 +363,7 @@ export function MoveCollectionsDialog({
                         <div className="relative mb-3">
                             <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                             <Input
-                                placeholder={i18n.t('Filter by collection name')}
+                                placeholder={t`Filter by collection name`}
                                 value={searchTerm}
                                 onChange={e => setSearchTerm(e.target.value)}
                                 className="pl-10"

+ 2 - 3
packages/dashboard/src/app/routes/_authenticated/_countries/countries.tsx

@@ -3,7 +3,7 @@ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { DeleteCountriesBulkAction } from './components/country-bulk-actions.js';
@@ -20,7 +20,7 @@ function CountryListPage() {
             pageId="country-list"
             listQuery={countriesListQuery}
             route={Route}
-            title="Countries"
+            title={<Trans>Countries</Trans>}
             defaultVisibility={{
                 name: true,
                 code: true,
@@ -47,7 +47,6 @@ function CountryListPage() {
             }}
             customizeColumns={{
                 name: {
-                    header: 'Name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
             }}

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx

@@ -18,7 +18,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { countryDetailDocument, createCountryDocument, updateCountryDocument } from './countries.graphql.js';
@@ -42,7 +42,7 @@ function CountryDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -61,14 +61,14 @@ function CountryDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast(i18n.t(creatingNewEntity ? 'Successfully created country' : 'Successfully updated country'));
+            toast(creatingNewEntity ? t`Successfully created country` : t`Successfully updated country`);
             form.reset(form.getValues());
             if (creatingNewEntity) {
                 await navigate({ to: `../$id`, params: { id: data.id } });
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create country' : 'Failed to update country'), {
+            toast(creatingNewEntity ? t`Failed to create country` : t`Failed to update country`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-sheet.tsx

@@ -7,7 +7,7 @@ import {
     SheetTitle,
     SheetTrigger,
 } from '@/vdb/components/ui/sheet.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { PanelLeftOpen } from 'lucide-react';
 import { CustomerGroupMembersTable } from './customer-group-members-table.js';
 

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx

@@ -7,7 +7,7 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQueryClient } from '@tanstack/react-query';
 import { Link } from '@tanstack/react-router';
 import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
@@ -46,19 +46,19 @@ export function CustomerGroupMembersTable({
     const [page, setPage] = useState(1);
     const [pageSize, setPageSize] = useState(10);
     const [filters, setFilters] = useState<ColumnFiltersState>([]);
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const queryClient = useQueryClient();
 
     const { mutate: addCustomerToGroup } = useMutation({
         mutationFn: api.mutate(addCustomerToGroupDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Customer added to group'));
+            toast.success(t`Customer added to group`);
             queryClient.invalidateQueries({
                 queryKey: [PaginatedListDataTableKey, customerGroupMemberListDocument],
             });
         },
         onError: () => {
-            toast.error(i18n.t('Failed to add customer to group'));
+            toast.error(t`Failed to add customer to group`);
         },
     });
 

+ 2 - 4
packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx

@@ -3,7 +3,7 @@ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { DeleteCustomerGroupsBulkAction } from './components/customer-group-bulk-actions.js';
@@ -19,16 +19,14 @@ function CustomerGroupListPage() {
     return (
         <ListPage
             pageId="customer-group-list"
-            title="Customer Groups"
+            title={<Trans>Customer Groups</Trans>}
             listQuery={customerGroupListDocument}
             route={Route}
             customizeColumns={{
                 name: {
-                    header: 'Name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
                 customers: {
-                    header: () => <Trans>Values</Trans>,
                     cell: ({ cell }) => {
                         const value = cell.getValue();
                         if (!value) {

+ 13 - 6
packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx

@@ -16,7 +16,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { CustomerGroupMembersTable } from './components/customer-group-members-table.js';
@@ -45,7 +45,7 @@ function CustomerGroupDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -61,16 +61,23 @@ function CustomerGroupDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast.success(i18n.t(creatingNewEntity ? 'Successfully created customer group' : 'Successfully updated customer group'));
+            toast.success(
+                creatingNewEntity
+                    ? t`Successfully created customer group`
+                    : t`Successfully updated customer group`,
+            );
             resetForm();
             if (creatingNewEntity && data?.id) {
                 await navigate({ to: `../$id`, params: { id: data.id } });
             }
         },
         onError: err => {
-            toast.error(i18n.t(creatingNewEntity ? 'Failed to create customer group' : 'Failed to update customer group'), {
-                description: err instanceof Error ? err.message : 'Unknown error',
-            });
+            toast.error(
+                creatingNewEntity ? t`Failed to create customer group` : t`Failed to update customer group`,
+                {
+                    description: err instanceof Error ? err.message : 'Unknown error',
+                },
+            );
         },
     });
 

+ 8 - 8
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx

@@ -11,7 +11,7 @@ import {
     DialogTrigger,
 } from '@/vdb/components/ui/dialog.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { EditIcon, TrashIcon } from 'lucide-react';
 import { useState } from 'react';
@@ -37,15 +37,15 @@ export function CustomerAddressCard({
     onDelete?: () => void;
 }) {
     const [open, setOpen] = useState(false);
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const { mutate: deleteAddress } = useMutation({
         mutationFn: api.mutate(deleteCustomerAddressDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Address deleted successfully'));
+            toast.success(t`Address deleted successfully`);
             onDelete?.();
         },
         onError: () => {
-            toast.error(i18n.t('Failed to delete address'));
+            toast.error(t`Failed to delete address`);
         },
     });
 
@@ -53,11 +53,11 @@ export function CustomerAddressCard({
     const { mutate: updateAddress } = useMutation({
         mutationFn: api.mutate(updateCustomerAddressDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Address updated successfully'));
+            toast.success(t`Address updated successfully`);
             onUpdate?.();
         },
         onError: error => {
-            toast.error(i18n.t('Failed to update address'));
+            toast.error(t`Failed to update address`);
             console.error('Error updating address:', error);
         },
     });
@@ -138,8 +138,8 @@ export function CustomerAddressCard({
                     )}
                     {deletable && (
                         <ConfirmationDialog
-                            title={i18n.t('Delete Address')}
-                            description={i18n.t('Are you sure you want to delete this address?')}
+                            title={t`Delete Address`}
+                            description={t`Are you sure you want to delete this address?`}
                             onConfirm={() => {
                                 deleteAddress({ id: address.id });
                                 onDelete?.();

+ 3 - 3
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-address-form.tsx

@@ -13,8 +13,8 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql, ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useQuery } from '@tanstack/react-query';
 import { useForm } from 'react-hook-form';
 import { z } from 'zod';
@@ -59,7 +59,7 @@ interface CustomerAddressFormProps {
 }
 
 export function CustomerAddressForm({ address, onSubmit, onCancel }: Readonly<CustomerAddressFormProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     // Fetch available countries
     const { data: countriesData, isLoading: isLoadingCountries } = useQuery({
@@ -246,7 +246,7 @@ export function CustomerAddressForm({ address, onSubmit, onCancel }: Readonly<Cu
                                 >
                                     <FormControl>
                                         <SelectTrigger>
-                                            <SelectValue placeholder={i18n.t('Select a country')} />
+                                            <SelectValue placeholder={t`Select a country`} />
                                         </SelectTrigger>
                                     </FormControl>
                                     <SelectContent>

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx

@@ -1,7 +1,7 @@
 import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { Skeleton } from '@/vdb/components/ui/skeleton.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { TriangleAlert } from 'lucide-react';
 import { CustomerHistory } from './customer-history.js';
 import { useCustomerHistory } from './use-customer-history.js';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx

@@ -1,5 +1,5 @@
 import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/index.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { CheckIcon, Edit3, KeyIcon, Mail, MapPin, SquarePen, User, UserCheck, Users } from 'lucide-react';
 import { CustomerHistoryCustomerDetail } from './customer-history-types.js';
 

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx

@@ -1,5 +1,5 @@
 import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 
 export function CustomerRegisteredOrVerifiedComponent(props: Readonly<HistoryEntryProps>) {
     const { entry } = props;

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-history/use-customer-history.ts

@@ -1,6 +1,6 @@
 import { api } from '@/vdb/graphql/api.js';
 import { graphql, ResultOf } from '@/vdb/graphql/graphql.js';
-import { useLingui } from '@/vdb/lib/trans.js';
+import { useLingui } from '@lingui/react/macro';
 import { useInfiniteQuery, useMutation } from '@tanstack/react-query';
 import { useState } from 'react';
 import { toast } from 'sonner';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/components/customer-status-badge.tsx

@@ -1,5 +1,5 @@
 import { Badge } from '@/vdb/components/ui/badge.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { BadgeCheck, BadgeX } from 'lucide-react';
 
 export type CustomerStatus = 'guest' | 'registered' | 'verified';

+ 5 - 4
packages/dashboard/src/app/routes/_authenticated/_customers/customers.tsx

@@ -3,7 +3,7 @@ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { DeleteCustomersBulkAction } from './components/customer-bulk-actions.js';
@@ -18,7 +18,7 @@ export const Route = createFileRoute('/_authenticated/_customers/customers')({
 function CustomerListPage() {
     return (
         <ListPage
-            title="Customers"
+            title={<Trans>Customers</Trans>}
             pageId="customer-list"
             listQuery={customerListDocument}
             onSearchTermChange={searchTerm => {
@@ -42,7 +42,7 @@ function CustomerListPage() {
             route={Route}
             customizeColumns={{
                 user: {
-                    header: 'Status',
+                    header: () => <Trans>Status</Trans>,
                     cell: ({ cell }) => {
                         const value = cell.getValue();
                         return <CustomerStatusBadge user={value} />;
@@ -51,7 +51,8 @@ function CustomerListPage() {
             }}
             additionalColumns={{
                 name: {
-                    header: 'Name',
+                    id: 'name',
+                    header: () => <Trans>Name</Trans>,
                     cell: ({ row }) => {
                         const value = `${row.original.firstName} ${row.original.lastName}`;
                         return <DetailPageButton id={row.original.id} label={value} />;

+ 10 - 8
packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx

@@ -27,7 +27,7 @@ import {
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { Plus } from 'lucide-react';
@@ -66,7 +66,7 @@ function CustomerDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [newAddressOpen, setNewAddressOpen] = useState(false);
 
     const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
@@ -89,19 +89,21 @@ function CustomerDetailPage() {
         params: { id: params.id },
         onSuccess: async data => {
             if (data.__typename === 'Customer') {
-                toast.success(i18n.t(creatingNewEntity ? 'Successfully created customer' : 'Successfully updated customer'));
+                toast.success(
+                    creatingNewEntity ? t`Successfully created customer` : t`Successfully updated customer`,
+                );
                 resetForm();
                 if (creatingNewEntity) {
                     await navigate({ to: `../$id`, params: { id: data.id } });
                 }
             } else {
-                toast.error(i18n.t(creatingNewEntity ? 'Failed to create customer' : 'Failed to update customer'), {
+                toast.error(creatingNewEntity ? t`Failed to create customer` : t`Failed to update customer`, {
                     description: data.message,
                 });
             }
         },
         onError: err => {
-            toast.error(i18n.t(creatingNewEntity ? 'Failed to create customer' : 'Failed to update customer'), {
+            toast.error(creatingNewEntity ? t`Failed to create customer` : t`Failed to update customer`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },
@@ -114,7 +116,7 @@ function CustomerDetailPage() {
             refreshEntity();
         },
         onError: () => {
-            toast.error(i18n.t('Failed to create address'));
+            toast.error(t`Failed to create address`);
         },
     });
 
@@ -124,7 +126,7 @@ function CustomerDetailPage() {
             refreshEntity();
         },
         onError: () => {
-            toast(i18n.t('Failed to add customer to group'));
+            toast(t`Failed to add customer to group`);
         },
     });
 
@@ -134,7 +136,7 @@ function CustomerDetailPage() {
             refreshEntity();
         },
         onError: () => {
-            toast(i18n.t('Failed to remove customer from group'));
+            toast(t`Failed to remove customer from group`);
         },
     });
 

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx

@@ -5,7 +5,7 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { useForm } from 'react-hook-form';
 

+ 6 - 5
packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx

@@ -6,7 +6,7 @@ import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-ta
 import { api } from '@/vdb/graphql/api.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { useLingui } from '@/vdb/lib/trans.js';
+import { useLingui } from '@lingui/react/macro';
 import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
 import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
 
@@ -46,7 +46,7 @@ export const AssignFacetsToChannelBulkAction: BulkActionComponent<any> = ({ sele
 
 export const RemoveFacetsFromChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
     const { activeChannel } = useChannel();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     return (
         <RemoveFromChannelBulkAction
@@ -68,15 +68,16 @@ export const RemoveFacetsFromChannelBulkAction: BulkActionComponent<any> = ({ se
                         if ('id' in item) {
                             // Do nothing
                         } else if ('message' in item) {
-                            errors.push(item.message);
-                            toast.error(i18n.t(`Failed to remove facet from channel: ${item.message}`));
+                            const message = item.message;
+                            errors.push(message);
+                            toast.error(t`Failed to remove facet from channel: ${message}`);
                         }
                     }
 
                     const successCount = selection.length - errors.length;
 
                     if (successCount > 0) {
-                        toast.success(i18n.t(`Successfully removed ${successCount} facets from channel`));
+                        toast.success(t`Successfully removed ${successCount} facets from channel`);
                     }
                 }
             }}

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx

@@ -7,7 +7,7 @@ import {
     SheetTitle,
     SheetTrigger,
 } from '@/vdb/components/ui/sheet.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { PanelLeftOpen } from 'lucide-react';
 import { FacetValuesTable } from './facet-values-table.js';
 

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx

@@ -2,7 +2,7 @@ import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js'
 import { PaginatedListDataTable } from '@/vdb/components/shared/paginated-list-data-table.js';
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { Link } from '@tanstack/react-router';
 import { Button } from '@/vdb/components/ui/button.js';
 import { ColumnFiltersState, SortingState } from '@tanstack/react-table';

+ 5 - 5
packages/dashboard/src/app/routes/_authenticated/_facets/facets.tsx

@@ -1,3 +1,4 @@
+import { DataTableCellComponent } from '@/vdb/components/data-table/types.js';
 import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
 import { FacetValueChip } from '@/vdb/components/shared/facet-value-chip.js';
 import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
@@ -5,7 +6,7 @@ import { Badge } from '@/vdb/components/ui/badge.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { ResultOf } from 'gql.tada';
 import { PlusIcon } from 'lucide-react';
@@ -16,8 +17,7 @@ import {
     RemoveFacetsFromChannelBulkAction,
 } from './components/facet-bulk-actions.js';
 import { FacetValuesSheet } from './components/facet-values-sheet.js';
-import { deleteFacetDocument, facetListDocument } from './facets.graphql.js';
-import { DataTableCellComponent } from '@/vdb/components/data-table/types.js';
+import { facetListDocument } from './facets.graphql.js';
 
 export const Route = createFileRoute('/_authenticated/_facets/facets')({
     component: FacetListPage,
@@ -61,7 +61,7 @@ function FacetListPage() {
     return (
         <ListPage
             pageId="facet-list"
-            title="Facets"
+            title={<Trans>Facets</Trans>}
             listQuery={facetListDocument}
             defaultVisibility={{
                 name: true,
@@ -70,7 +70,7 @@ function FacetListPage() {
             }}
             customizeColumns={{
                 name: {
-                    header: () => <Trans>Facet Name</Trans>,
+                    id: 'name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
                 isPrivate: {

+ 7 - 5
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx

@@ -1,3 +1,4 @@
+import { PageBreadcrumb } from '@/vdb/components/layout/generated-breadcrumbs.js';
 import { ErrorPage } from '@/vdb/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
@@ -17,7 +18,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import {
@@ -25,7 +26,6 @@ import {
     facetValueDetailDocument,
     updateFacetValueDocument,
 } from './facets.graphql.js';
-import { PageBreadcrumb } from '@/vdb/components/layout/generated-breadcrumbs.js';
 
 const pageId = 'facet-value-detail';
 
@@ -52,7 +52,7 @@ function FacetValueDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -81,7 +81,9 @@ function FacetValueDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast(i18n.t(creatingNewEntity ? 'Successfully created facet value' : 'Successfully updated facet value'));
+            toast(
+                creatingNewEntity ? t`Successfully created facet value` : t`Successfully updated facet value`,
+            );
             resetForm();
             const created = Array.isArray(data) ? data[0] : data;
             if (creatingNewEntity && created) {
@@ -89,7 +91,7 @@ function FacetValueDetailPage() {
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create facet value' : 'Failed to update facet value'), {
+            toast(creatingNewEntity ? t`Failed to create facet value` : t`Failed to update facet value`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx

@@ -19,7 +19,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { FacetValuesTable } from './components/facet-values-table.js';
@@ -46,7 +46,7 @@ function FacetDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -76,14 +76,14 @@ function FacetDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast(i18n.t(creatingNewEntity ? 'Successfully created facet' : 'Successfully updated facet'));
+            toast(creatingNewEntity ? t`Successfully created facet` : t`Successfully updated facet`);
             resetForm();
             if (creatingNewEntity) {
                 await navigate({ to: `../$id`, params: { id: data.id } });
             }
         },
         onError: err => {
-            toast(i18n.t(creatingNewEntity ? 'Failed to create facet' : 'Failed to update facet'), {
+            toast(creatingNewEntity ? t`Failed to create facet` : t`Failed to update facet`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 5 - 5
packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx

@@ -19,7 +19,7 @@ import {
     PageTitle,
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { globalSettingsDocument, updateGlobalSettingsDocument } from './global-settings.graphql.js';
@@ -48,7 +48,7 @@ function GlobalSettingsPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending } = useDetailPage({
         queryDocument: globalSettingsDocument,
@@ -67,19 +67,19 @@ function GlobalSettingsPage() {
         params: { id: 'undefined' },
         onSuccess: async data => {
             if (data.__typename === 'GlobalSettings') {
-                toast(i18n.t('Successfully updated global settings'));
+                toast(t`Successfully updated global settings`);
                 form.reset(form.getValues());
                 if (creatingNewEntity) {
                     await navigate({ to: `../$id`, params: { id: data.id } });
                 }
             } else {
-                toast(i18n.t('Failed to update global settings'), {
+                toast(t`Failed to update global settings`, {
                     description: data.message,
                 });
             }
         },
         onError: err => {
-            toast(i18n.t('Failed to update global settings'), {
+            toast(t`Failed to update global settings`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 19 - 21
packages/dashboard/src/app/routes/_authenticated/_orders/components/add-manual-payment-dialog.tsx

@@ -16,15 +16,12 @@ import { Form } from '@/vdb/components/ui/form.js';
 import { Input } from '@/vdb/components/ui/input.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { useState } from 'react';
 import { useForm } from 'react-hook-form';
 import { toast } from 'sonner';
-import {
-    addManualPaymentToOrderDocument,
-    paymentMethodsDocument
-} from '../orders.graphql.js';
+import { addManualPaymentToOrderDocument, paymentMethodsDocument } from '../orders.graphql.js';
 import { Order } from '../utils/order-types.js';
 import { calculateOutstandingPaymentAmount } from '../utils/order-utils.js';
 
@@ -39,7 +36,7 @@ interface FormData {
 }
 
 export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualPaymentDialogProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const { formatCurrency } = useLocalFormat();
     const [isSubmitting, setIsSubmitting] = useState(false);
     const [open, setOpen] = useState(false);
@@ -49,16 +46,16 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
         onSuccess: (result: any) => {
             const { addManualPaymentToOrder } = result;
             if (addManualPaymentToOrder.__typename === 'Order') {
-                toast(i18n.t('Successfully added payment to order'));
+                toast(t`Successfully added payment to order`);
                 onSuccess?.();
             } else {
-                toast(i18n.t('Failed to add payment'), {
+                toast(t`Failed to add payment`, {
                     description: addManualPaymentToOrder.message,
                 });
             }
         },
         onError: error => {
-            toast(i18n.t('Failed to add payment'), {
+            toast(t`Failed to add payment`, {
                 description: error instanceof Error ? error.message : 'Unknown error',
             });
         },
@@ -86,7 +83,7 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
             setOpen(false);
             form.reset();
         } catch (error) {
-            toast(i18n.t('Failed to add payment'), {
+            toast(t`Failed to add payment`, {
                 description: error instanceof Error ? error.message : 'Unknown error',
             });
         } finally {
@@ -107,7 +104,7 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
         listQuery: paymentMethodsDocument,
         idKey: 'code',
         labelKey: 'name',
-        placeholder: i18n.t('Search payment methods...'),
+        placeholder: t`Search payment methods...`,
         multiple: false,
         label: (method: any) => `${method.name} (${method.code})`,
     });
@@ -136,15 +133,18 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
                         </DialogDescription>
                     </DialogHeader>
                     <Form {...form}>
-                        <form onSubmit={e => {
-                            e.stopPropagation();
-                            form.handleSubmit(handleSubmit)(e);
-                        }} className="space-y-4">
+                        <form
+                            onSubmit={e => {
+                                e.stopPropagation();
+                                form.handleSubmit(handleSubmit)(e);
+                            }}
+                            className="space-y-4"
+                        >
                             <FormFieldWrapper
                                 control={form.control}
                                 name="method"
                                 label={<Trans>Payment method</Trans>}
-                                rules={{ required: i18n.t('Payment method is required') }}
+                                rules={{ required: t`Payment method is required` }}
                                 render={({ field }) => (
                                     <RelationSelector
                                         config={paymentMethodSelectorConfig}
@@ -158,9 +158,9 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
                                 control={form.control}
                                 name="transactionId"
                                 label={<Trans>Transaction ID</Trans>}
-                                rules={{ required: i18n.t('Transaction ID is required') }}
+                                rules={{ required: t`Transaction ID is required` }}
                                 render={({ field }) => (
-                                    <Input {...field} placeholder={i18n.t('Enter transaction ID')} />
+                                    <Input {...field} placeholder={t`Enter transaction ID`} />
                                 )}
                             />
                             <DialogFooter>
@@ -169,9 +169,7 @@ export function AddManualPaymentDialog({ order, onSuccess }: Readonly<AddManualP
                                 </Button>
                                 <Button
                                     type="submit"
-                                    disabled={
-                                        !form.formState.isValid || isSubmitting || !method
-                                    }
+                                    disabled={!form.formState.isValid || isSubmitting || !method}
                                 >
                                     {isSubmitting ? (
                                         <Trans>Adding...</Trans>

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx

@@ -3,7 +3,7 @@ import { Card } from '@/vdb/components/ui/card.js';
 import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql, ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { cn } from '@/vdb/lib/utils.js';
 import { useQuery } from '@tanstack/react-query';
 import { Plus } from 'lucide-react';

+ 22 - 22
packages/dashboard/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx

@@ -5,7 +5,7 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { Input } from '@/vdb/components/ui/input.js';
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/vdb/components/ui/table.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import {
     ColumnDef,
     flexRender,
@@ -53,16 +53,16 @@ export interface OrderTableProps {
 }
 
 export function EditOrderTable({
-    order,
-    eligibleShippingMethods,
-    onAddItem,
-    onAdjustLine,
-    onRemoveLine,
-    onSetShippingMethod,
-    onApplyCouponCode,
-    onRemoveCouponCode,
-    displayTotals = true,
-}: Readonly<OrderTableProps>) {
+                                   order,
+                                   eligibleShippingMethods,
+                                   onAddItem,
+                                   onAdjustLine,
+                                   onRemoveLine,
+                                   onSetShippingMethod,
+                                   onApplyCouponCode,
+                                   onRemoveCouponCode,
+                                   displayTotals = true,
+                               }: Readonly<OrderTableProps>) {
     const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
     const currencyCode = order.currencyCode;
     const columns: ColumnDef<OrderLineFragment & { customFields?: Record<string, any> }>[] = [
@@ -169,9 +169,9 @@ export function EditOrderTable({
                                             {header.isPlaceholder
                                                 ? null
                                                 : flexRender(
-                                                      header.column.columnDef.header,
-                                                      header.getContext(),
-                                                  )}
+                                                    header.column.columnDef.header,
+                                                    header.getContext(),
+                                                )}
                                         </TableHead>
                                     );
                                 })}
@@ -181,14 +181,14 @@ export function EditOrderTable({
                     <TableBody>
                         {table.getRowModel().rows?.length
                             ? table.getRowModel().rows.map(row => (
-                                  <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
-                                      {row.getVisibleCells().map(cell => (
-                                          <TableCell key={cell.id}>
-                                              {flexRender(cell.column.columnDef.cell, cell.getContext())}
-                                          </TableCell>
-                                      ))}
-                                  </TableRow>
-                              ))
+                                <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
+                                    {row.getVisibleCells().map(cell => (
+                                        <TableCell key={cell.id}>
+                                            {flexRender(cell.column.columnDef.cell, cell.getContext())}
+                                        </TableCell>
+                                    ))}
+                                </TableRow>
+                            ))
                             : null}
                         <TableRow>
                             <TableCell colSpan={columns.length} className="h-12">

+ 6 - 6
packages/dashboard/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx

@@ -14,7 +14,7 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { Label } from '@/vdb/components/ui/label.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { ConfigurableOperationInput as ConfigurableOperationInputType } from '@vendure/common/lib/generated-types';
 import { useState } from 'react';
@@ -38,7 +38,7 @@ interface FulfillmentQuantity {
 }
 
 export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDialogProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [isSubmitting, setIsSubmitting] = useState(false);
     const [open, setOpen] = useState(false);
     const [fulfillmentQuantities, setFulfillmentQuantities] = useState<{
@@ -73,16 +73,16 @@ export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDi
         onSuccess: (result: any) => {
             const { addFulfillmentToOrder } = result;
             if (addFulfillmentToOrder.__typename === 'Fulfillment') {
-                toast(i18n.t('Successfully fulfilled order'));
+                toast(t`Successfully fulfilled order`);
                 onSuccess?.();
             } else {
-                toast(i18n.t('Failed to fulfill order'), {
+                toast(t`Failed to fulfill order`, {
                     description: addFulfillmentToOrder.message,
                 });
             }
         },
         onError: error => {
-            toast(i18n.t('Failed to fulfill order'), {
+            toast(t`Failed to fulfill order`, {
                 description: error instanceof Error ? error.message : 'Unknown error',
             });
         },
@@ -184,7 +184,7 @@ export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDi
             form.reset();
             setFulfillmentQuantities({});
         } catch (error) {
-            toast(i18n.t('Failed to fulfill order'), {
+            toast(t`Failed to fulfill order`, {
                 description: error instanceof Error ? error.message : 'Unknown error',
             });
         } finally {

+ 15 - 9
packages/dashboard/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx

@@ -2,8 +2,9 @@ import { LabeledData } from '@/vdb/components/labeled-data.js';
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/vdb/components/ui/collapsible.js';
 import { api } from '@/vdb/graphql/api.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
+import { useDynamicTranslations } from '@/vdb/hooks/use-dynamic-translations.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { ChevronDown } from 'lucide-react';
 import { toast } from 'sonner';
@@ -24,7 +25,8 @@ type FulfillmentDetailsProps = {
 
 export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<FulfillmentDetailsProps>) {
     const { formatDate } = useLocalFormat();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
+    const { getTranslatedFulfillmentState } = useDynamicTranslations();
 
     // Create a map of order lines by ID for quick lookup
     const orderLinesMap = new Map(order.lines.map(line => [line.id, line]));
@@ -34,14 +36,14 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
         onSuccess: (result: ResultOf<typeof transitionFulfillmentToStateDocument>) => {
             const fulfillment = result.transitionFulfillmentToState;
             if (fulfillment.__typename === 'Fulfillment') {
-                toast.success(i18n.t('Fulfillment state updated successfully'));
+                toast.success(t`Fulfillment state updated successfully`);
                 onSuccess?.();
             } else {
-                toast.error(fulfillment.message ?? i18n.t('Failed to update fulfillment state'));
+                toast.error(fulfillment.message ?? t`Failed to update fulfillment state`);
             }
         },
         onError: error => {
-            toast.error(i18n.t('Failed to update fulfillment state'));
+            toast.error(t`Failed to update fulfillment state`);
         },
     });
 
@@ -77,8 +79,9 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
 
         const suggested = nextSuggestedState();
         if (suggested) {
+            const suggestedState = getTranslatedFulfillmentState(suggested);
             actions.push({
-                label: `Transition to ${suggested}`,
+                label: t`Transition to ${suggestedState}`,
                 onClick: () => handleStateTransition(suggested),
                 disabled: transitionFulfillmentMutation.isPending,
             });
@@ -86,7 +89,7 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
 
         nextOtherStates().forEach(state => {
             actions.push({
-                label: `Transition to ${state}`,
+                label: t`Transition to ${getTranslatedFulfillmentState(state)}`,
                 type: getTypeForState(state),
                 onClick: () => handleStateTransition(state),
                 disabled: transitionFulfillmentMutation.isPending,
@@ -101,7 +104,10 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
             <div className="space-y-1">
                 <LabeledData label={<Trans>Fulfillment ID</Trans>} value={fulfillment.id.slice(-8)} />
                 <LabeledData label={<Trans>Method</Trans>} value={fulfillment.method} />
-                <LabeledData label={<Trans>State</Trans>} value={fulfillment.state} />
+                <LabeledData
+                    label={<Trans>State</Trans>}
+                    value={getTranslatedFulfillmentState(fulfillment.state)}
+                />
                 {fulfillment.trackingCode && (
                     <LabeledData label={<Trans>Tracking code</Trans>} value={fulfillment.trackingCode} />
                 )}
@@ -143,7 +149,7 @@ export function FulfillmentDetails({ order, fulfillment, onSuccess }: Readonly<F
 
             <div className="mt-3 pt-3 border-t">
                 <StateTransitionControl
-                    currentState={fulfillment.state}
+                    currentState={getTranslatedFulfillmentState(fulfillment.state)}
                     actions={getFulfillmentActions()}
                     isLoading={transitionFulfillmentMutation.isPending}
                 />

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-address.tsx

@@ -2,7 +2,7 @@ import { Separator } from '@/vdb/components/ui/separator.js';
 import { ResultOf } from 'gql.tada';
 import { Globe, Phone } from 'lucide-react';
 import { orderAddressFragment } from '../orders.graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 
 type OrderAddress = Omit<ResultOf<typeof orderAddressFragment>, 'country'> & {
     country: string | { code: string; name: string } | null;

+ 11 - 9
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx

@@ -13,7 +13,8 @@ import {
 import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { useDynamicTranslations } from '@/vdb/hooks/use-dynamic-translations.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQueryClient } from '@tanstack/react-query';
 import { Link, useNavigate } from '@tanstack/react-router';
 import { ResultOf } from 'gql.tada';
@@ -64,9 +65,10 @@ export function OrderDetailShared({
     titleSlot,
     beforeOrderTable,
 }: Readonly<OrderDetailSharedProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const navigate = useNavigate();
     const queryClient = useQueryClient();
+    const { getTranslatedOrderState } = useDynamicTranslations();
 
     const { form, submitHandler, entity, refreshEntity } = useDetailPage({
         pageId,
@@ -80,11 +82,11 @@ export function OrderDetailShared({
         },
         params: { id: orderId },
         onSuccess: async () => {
-            toast(i18n.t('Successfully updated order'));
+            toast(t`Successfully updated order`);
             form.reset(form.getValues());
         },
         onError: err => {
-            toast(i18n.t('Failed to update order'), {
+            toast(t`Failed to update order`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },
@@ -102,12 +104,12 @@ export function OrderDetailShared({
             return [];
         }
         return entity.nextStates.map((state: string) => ({
-            label: `Transition to ${state}`,
+            label: t`Transition to ${getTranslatedOrderState(state)}`,
             type: getTypeForState(state),
             onClick: async () => {
                 const transitionError = await transitionToState(state);
                 if (transitionError) {
-                    toast(i18n.t('Failed to transition order to state'), {
+                    toast(t`Failed to transition order to state`, {
                         description: transitionError,
                     });
                 } else {
@@ -115,7 +117,7 @@ export function OrderDetailShared({
                 }
             },
         }));
-    }, [entity, transitionToState, i18n]);
+    }, [entity, transitionToState, t]);
 
     if (!entity) {
         return null;
@@ -131,7 +133,7 @@ export function OrderDetailShared({
             await queryClient.invalidateQueries({ queryKey });
             await navigate({ to: `/orders/$id/modify`, params: { id: entity.id } });
         } catch (error) {
-            toast(i18n.t('Failed to modify order'), {
+            toast(t`Failed to modify order`, {
                 description: error instanceof Error ? error.message : 'Unknown error',
             });
         }
@@ -232,7 +234,7 @@ export function OrderDetailShared({
                 {/* Side Column Blocks */}
                 <PageBlock column="side" blockId="state">
                     <StateTransitionControl
-                        currentState={entity?.state}
+                        currentState={getTranslatedOrderState(entity?.state)}
                         actions={stateTransitionActions}
                         isLoading={transitionOrderToStateMutation.isPending}
                     />

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx

@@ -1,5 +1,5 @@
 import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 
 export function OrderStateTransitionComponent(props: Readonly<HistoryEntryProps>) {
     const { entry } = props;

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx

@@ -1,7 +1,7 @@
 import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { Skeleton } from '@/vdb/components/ui/skeleton.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { TriangleAlert } from 'lucide-react';
 import { OrderHistory } from './order-history.js';
 import { useOrderHistory } from './use-order-history.js';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx

@@ -1,5 +1,5 @@
 import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/index.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import {
     ArrowRightToLine,
     Ban,

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/use-order-history.ts

@@ -1,6 +1,6 @@
 import { api } from '@/vdb/graphql/api.js';
 import { graphql, ResultOf } from '@/vdb/graphql/graphql.js';
-import { useLingui } from '@/vdb/lib/trans.js';
+import { useLingui } from '@lingui/react/macro';
 import { QueryKey, useInfiniteQuery, useMutation } from '@tanstack/react-query';
 import { useState } from 'react';
 import { toast } from 'sonner';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx

@@ -2,7 +2,7 @@ import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js'
 import { Button } from '@/vdb/components/ui/button.js';
 import { Form } from '@/vdb/components/ui/form.js';
 import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { Settings2 } from 'lucide-react';
 import { useForm } from 'react-hook-form';
 

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx

@@ -17,7 +17,7 @@ import { Textarea } from '@/vdb/components/ui/textarea.js';
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { ResultOf, VariablesOf } from 'gql.tada';
 import { CheckIcon } from 'lucide-react';
@@ -53,7 +53,7 @@ export function OrderModificationPreviewDialog({
     modifyOrderInput,
     onResolve,
 }: Readonly<OrderModificationPreviewDialogProps>) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const { formatCurrency } = useLocalFormat();
     // Use a ref to track the last input sent to avoid duplicate calls
     const lastInputRef = useRef<ModifyOrderInput | null>(null);
@@ -99,7 +99,7 @@ export function OrderModificationPreviewDialog({
         previewMutation.data?.modifyOrder?.__typename === 'Order' ? previewMutation.data.modifyOrder : null;
     const error =
         previewMutation.data && previewMutation.data.modifyOrder?.__typename !== 'Order'
-            ? previewMutation.data.modifyOrder?.message || i18n.t('Unknown error')
+            ? previewMutation.data.modifyOrder?.message || t`Unknown error`
             : previewMutation.error?.message || null;
     const loading = previewMutation.isPending;
 
@@ -303,7 +303,7 @@ export function OrderModificationPreviewDialog({
                                                             <Textarea
                                                                 {...field}
                                                                 className="bg-background"
-                                                                placeholder={i18n.t('Enter refund note')}
+                                                                placeholder={t`Enter refund note`}
                                                             />
                                                         )}
                                                     />

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx

@@ -1,4 +1,4 @@
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { ResultOf, VariablesOf } from 'gql.tada';
 import { modifyOrderDocument, orderDetailDocument } from '../orders.graphql.js';
 

+ 27 - 27
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx

@@ -1,5 +1,5 @@
 import { TableCell, TableRow } from '@/vdb/components/ui/table.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { Order } from '../utils/order-types.js';
 import { MoneyGrossNet } from './money-gross-net.js';
 
@@ -15,35 +15,35 @@ export function OrderTableTotals({ order, columnCount }: Readonly<OrderTableTota
         <>
             {order.surcharges?.length > 0
                 ? order.surcharges.map((surcharge, index) => (
-                      <TableRow key={`${surcharge.description}-${index}`}>
-                          <TableCell colSpan={columnCount - 1} className="h-12">
-                              <Trans>Surcharge</Trans>: {surcharge.description}
-                          </TableCell>
-                          <TableCell colSpan={1} className="h-12">
-                              <MoneyGrossNet
-                                  priceWithTax={surcharge.priceWithTax}
-                                  price={surcharge.price}
-                                  currencyCode={currencyCode}
-                              />
-                          </TableCell>
-                      </TableRow>
-                  ))
+                    <TableRow key={`${surcharge.description}-${index}`}>
+                        <TableCell colSpan={columnCount - 1} className="h-12">
+                            <Trans>Surcharge</Trans>: {surcharge.description}
+                        </TableCell>
+                        <TableCell colSpan={1} className="h-12">
+                            <MoneyGrossNet
+                                priceWithTax={surcharge.priceWithTax}
+                                price={surcharge.price}
+                                currencyCode={currencyCode}
+                            />
+                        </TableCell>
+                    </TableRow>
+                ))
                 : null}
             {order.discounts?.length > 0
                 ? order.discounts.map((discount, index) => (
-                      <TableRow key={`${discount.description}-${index}`}>
-                          <TableCell colSpan={columnCount - 1} className="h-12">
-                              <Trans>Discount</Trans>: {discount.description}
-                          </TableCell>
-                          <TableCell colSpan={1} className="h-12">
-                              <MoneyGrossNet
-                                  priceWithTax={discount.amountWithTax}
-                                  price={discount.amount}
-                                  currencyCode={currencyCode}
-                              />
-                          </TableCell>
-                      </TableRow>
-                  ))
+                    <TableRow key={`${discount.description}-${index}`}>
+                        <TableCell colSpan={columnCount - 1} className="h-12">
+                            <Trans>Discount</Trans>: {discount.description}
+                        </TableCell>
+                        <TableCell colSpan={1} className="h-12">
+                            <MoneyGrossNet
+                                priceWithTax={discount.amountWithTax}
+                                price={discount.amount}
+                                currencyCode={currencyCode}
+                            />
+                        </TableCell>
+                    </TableRow>
+                ))
                 : null}
             <TableRow>
                 <TableCell colSpan={columnCount - 1} className="h-12">

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-table.tsx

@@ -10,7 +10,7 @@ import { addCustomFields } from '@/vdb/framework/document-introspection/add-cust
 import { getFieldsFromDocumentNode } from '@/vdb/framework/document-introspection/get-document-structure.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { JsonEditor } from 'json-edit-react';
 import { EllipsisVertical } from 'lucide-react';
 import { useMemo } from 'react';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-tax-summary.tsx

@@ -1,6 +1,6 @@
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/vdb/components/ui/table.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { Order } from '../utils/order-types.js';
 
 export function OrderTaxSummary({ order }: Readonly<{ order: Order }>) {

+ 26 - 20
packages/dashboard/src/app/routes/_authenticated/_orders/components/payment-details.tsx

@@ -3,8 +3,9 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/vdb/components/ui/collapsible.js';
 import { api } from '@/vdb/graphql/api.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
+import { useDynamicTranslations } from '@/vdb/hooks/use-dynamic-translations.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { JsonEditor } from 'json-edit-react';
 import { ChevronDown } from 'lucide-react';
@@ -32,7 +33,8 @@ type PaymentDetailsProps = {
 
 export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<PaymentDetailsProps>) {
     const { formatCurrency, formatDate } = useLocalFormat();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
+    const { getTranslatedPaymentState, getTranslatedRefundState } = useDynamicTranslations();
     const [settleRefundDialogOpen, setSettleRefundDialogOpen] = useState(false);
     const [selectedRefundId, setSelectedRefundId] = useState<string | null>(null);
 
@@ -40,14 +42,14 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
         mutationFn: api.mutate(settlePaymentDocument),
         onSuccess: (result: ResultOf<typeof settlePaymentDocument>) => {
             if (result.settlePayment.__typename === 'Payment') {
-                toast.success(i18n.t('Payment settled successfully'));
+                toast.success(t`Payment settled successfully`);
                 onSuccess?.();
             } else {
-                toast.error(result.settlePayment.message ?? i18n.t('Failed to settle payment'));
+                toast.error(result.settlePayment.message ?? t`Failed to settle payment`);
             }
         },
         onError: () => {
-            toast.error(i18n.t('Failed to settle payment'));
+            toast.error(t`Failed to settle payment`);
         },
     });
 
@@ -55,16 +57,14 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
         mutationFn: api.mutate(transitionPaymentToStateDocument),
         onSuccess: (result: ResultOf<typeof transitionPaymentToStateDocument>) => {
             if (result.transitionPaymentToState.__typename === 'Payment') {
-                toast.success(i18n.t('Payment state updated successfully'));
+                toast.success(t`Payment state updated successfully`);
                 onSuccess?.();
             } else {
-                toast.error(
-                    result.transitionPaymentToState.message ?? i18n.t('Failed to update payment state'),
-                );
+                toast.error(result.transitionPaymentToState.message ?? t`Failed to update payment state`);
             }
         },
         onError: () => {
-            toast.error(i18n.t('Failed to update payment state'));
+            toast.error(t`Failed to update payment state`);
         },
     });
 
@@ -72,14 +72,14 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
         mutationFn: api.mutate(cancelPaymentDocument),
         onSuccess: (result: ResultOf<typeof cancelPaymentDocument>) => {
             if (result.cancelPayment.__typename === 'Payment') {
-                toast.success(i18n.t('Payment cancelled successfully'));
+                toast.success(t`Payment cancelled successfully`);
                 onSuccess?.();
             } else {
-                toast.error(result.cancelPayment.message ?? i18n.t('Failed to cancel payment'));
+                toast.error(result.cancelPayment.message ?? t`Failed to cancel payment`);
             }
         },
         onError: () => {
-            toast.error(i18n.t('Failed to cancel payment'));
+            toast.error(t`Failed to cancel payment`);
         },
     });
 
@@ -87,15 +87,15 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
         mutationFn: api.mutate(settleRefundDocument),
         onSuccess: (result: ResultOf<typeof settleRefundDocument>) => {
             if (result.settleRefund.__typename === 'Refund') {
-                toast.success(i18n.t('Refund settled successfully'));
+                toast.success(t`Refund settled successfully`);
                 onSuccess?.();
                 setSettleRefundDialogOpen(false);
             } else {
-                toast.error(result.settleRefund.message ?? i18n.t('Failed to settle refund'));
+                toast.error(result.settleRefund.message ?? t`Failed to settle refund`);
             }
         },
         onError: () => {
-            toast.error(i18n.t('Failed to settle refund'));
+            toast.error(t`Failed to settle refund`);
         },
     });
 
@@ -139,7 +139,7 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
 
         if (payment.nextStates?.includes('Settled')) {
             actions.push({
-                label: 'Settle payment',
+                label: t`Settle payment`,
                 onClick: handleSettlePayment,
                 type: 'success',
                 disabled: settlePaymentMutation.isPending,
@@ -148,7 +148,10 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
 
         nextOtherStates().forEach(state => {
             actions.push({
-                label: state === 'Cancelled' ? 'Cancel payment' : `Transition to ${state}`,
+                label:
+                    state === 'Cancelled'
+                        ? t`Cancel payment`
+                        : t`Transition to ${getTranslatedPaymentState(state)}`,
                 type: getTypeForState(state),
                 onClick: () => handlePaymentStateTransition(state),
                 disabled: transitionPaymentMutation.isPending || cancelPaymentMutation.isPending,
@@ -210,7 +213,10 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
                                 <div key={refund.id} className="p-3 border rounded-md bg-muted/50">
                                     <div className="space-y-1">
                                         <LabeledData label={<Trans>Refund ID</Trans>} value={refund.id} />
-                                        <LabeledData label={<Trans>State</Trans>} value={refund.state} />
+                                        <LabeledData
+                                            label={<Trans>State</Trans>}
+                                            value={getTranslatedRefundState(refund.state)}
+                                        />
                                         <LabeledData
                                             label={<Trans>Created at</Trans>}
                                             value={formatDate(refund.createdAt, {
@@ -266,7 +272,7 @@ export function PaymentDetails({ payment, currencyCode, onSuccess }: Readonly<Pa
                 )}
                 <div className="mt-3 pt-3 border-t">
                     <StateTransitionControl
-                        currentState={payment.state}
+                        currentState={getTranslatedPaymentState(payment.state)}
                         actions={getPaymentActions()}
                         isLoading={
                             settlePaymentMutation.isPending ||

+ 3 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx

@@ -1,6 +1,7 @@
 import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
 import { Badge } from '@/vdb/components/ui/badge.js';
 import { api } from '@/vdb/graphql/api.js';
+import { useDynamicTranslations } from '@/vdb/hooks/use-dynamic-translations.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { useQuery } from '@tanstack/react-query';
 import { sellerOrdersDocument } from '../orders.graphql.js';
@@ -12,6 +13,7 @@ export interface SellerOrdersCardProps {
 
 export function SellerOrdersCard({ orderId }: Readonly<SellerOrdersCardProps>) {
     const { formatCurrency } = useLocalFormat();
+    const { getTranslatedOrderState } = useDynamicTranslations();
     const { data, isLoading, error } = useQuery({
         queryKey: ['seller-orders', orderId],
         queryFn: () => api.query(sellerOrdersDocument, { orderId }),
@@ -51,7 +53,7 @@ export function SellerOrdersCard({ orderId }: Readonly<SellerOrdersCardProps>) {
                             <div className="flex gap-2">
                                 {seller && <Badge variant={'secondary'}>{seller.name}</Badge>}
                             </div>
-                            <Badge variant={'secondary'}>{sellerOrder.state}</Badge>
+                            <Badge variant={'secondary'}>{getTranslatedOrderState(sellerOrder.state)}</Badge>
                         </div>
                     </div>
                 );

+ 6 - 6
packages/dashboard/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx

@@ -9,7 +9,7 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { Input } from '@/vdb/components/ui/input.js';
 import { Label } from '@/vdb/components/ui/label.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { useState } from 'react';
 
 type SettleRefundDialogProps = {
@@ -20,11 +20,11 @@ type SettleRefundDialogProps = {
 };
 
 export function SettleRefundDialog({
-    open,
-    onOpenChange,
-    onSettle,
-    isLoading,
-}: Readonly<SettleRefundDialogProps>) {
+                                       open,
+                                       onOpenChange,
+                                       onSettle,
+                                       isLoading,
+                                   }: Readonly<SettleRefundDialogProps>) {
     const [transactionId, setTransactionId] = useState('');
 
     const handleSettle = () => {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx

@@ -1,7 +1,7 @@
 import { Money } from '@/vdb/components/data-display/money.js';
 import { Card, CardContent, CardHeader, CardTitle } from '@/vdb/components/ui/card.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { draftOrderEligibleShippingMethodsDocument } from '../orders.graphql.js';
 
 type ShippingMethodQuote = ResultOf<

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx

@@ -5,7 +5,7 @@ import {
     DropdownMenuItem,
     DropdownMenuTrigger,
 } from '@/vdb/components/ui/dropdown-menu.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { cn } from '@/vdb/lib/utils.js';
 import { EllipsisVertical, CircleDashed, CircleCheck, CircleX } from 'lucide-react';
 

+ 3 - 2
packages/dashboard/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx

@@ -10,7 +10,7 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { ResultOf } from 'gql.tada';
 import { useState } from 'react';
@@ -22,7 +22,8 @@ import { orderHistoryDocument, transitionOrderToStateDocument } from '../orders.
  */
 export function useTransitionOrderToState(orderId: string | undefined) {
     const [selectStateOpen, setSelectStateOpen] = useState(false);
-    const [onSuccessFn, setOnSuccessFn] = useState<() => void>(() => {});
+    const [onSuccessFn, setOnSuccessFn] = useState<() => void>(() => {
+    });
     const { data, isLoading, error } = useQuery({
         queryKey: ['orderPreModifyingState', orderId],
         queryFn: async () => {

+ 5 - 9
packages/dashboard/src/app/routes/_authenticated/_orders/orders.tsx

@@ -10,7 +10,7 @@ import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { api } from '@/vdb/graphql/api.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
 import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
@@ -24,6 +24,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders')({
 function OrderListPage() {
     const serverConfig = useServerConfig();
     const navigate = useNavigate();
+    const { t } = useLingui();
     const { mutate: createDraftOrder } = useMutation({
         mutationFn: api.mutate(createDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof createDraftOrderDocument>) => {
@@ -33,7 +34,7 @@ function OrderListPage() {
     return (
         <ListPage
             pageId="order-list"
-            title="Orders"
+            title={<Trans>Orders</Trans>}
             onSearchTermChange={searchTerm => {
                 return {
                     _or: [
@@ -60,19 +61,15 @@ function OrderListPage() {
             route={Route}
             customizeColumns={{
                 total: {
-                    header: 'Total',
                     cell: OrderMoneyCell,
                 },
                 totalWithTax: {
-                    header: 'Total with Tax',
                     cell: OrderMoneyCell,
                 },
                 state: {
-                    header: 'State',
                     cell: OrderStateCell,
                 },
                 code: {
-                    header: 'Code',
                     cell: ({ cell, row }) => {
                         const value = cell.getValue() as string;
                         const id = row.original.id;
@@ -80,11 +77,10 @@ function OrderListPage() {
                     },
                 },
                 customer: {
-                    header: 'Customer',
                     cell: CustomerCell,
                 },
                 shippingLines: {
-                    header: 'Shipping',
+                    header: () => <Trans>Shipping</Trans>,
                     cell: ({ row }) => {
                         const value = row.original.shippingLines;
                         return <div>{value.map(line => line.shippingMethod.name).join(', ')}</div>;
@@ -100,7 +96,7 @@ function OrderListPage() {
             }}
             facetedFilters={{
                 state: {
-                    title: 'State',
+                    title: t`State`,
                     options:
                         serverConfig?.orderProcess.map(state => {
                             return {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx

@@ -1,7 +1,7 @@
 import { ErrorPage } from '@/vdb/components/shared/error-page.js';
 import { Badge } from '@/vdb/components/ui/badge.js';
 import { Button } from '@/vdb/components/ui/button.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { ArrowLeft } from 'lucide-react';
 import { OrderDetail, OrderDetailShared } from './components/order-detail-shared.js';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx

@@ -1,6 +1,6 @@
 import { ErrorPage } from '@/vdb/components/shared/error-page.js';
 import { PageBlock } from '@/vdb/framework/layout-engine/page-layout.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute } from '@tanstack/react-router';
 import { OrderDetailShared } from './components/order-detail-shared.js';
 import { SellerOrdersCard } from './components/seller-orders-card.js';

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx

@@ -10,7 +10,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useQuery, useQueryClient } from '@tanstack/react-query';
 import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
 import { VariablesOf } from 'gql.tada';
@@ -71,7 +71,7 @@ type ProductVariantInfo = {
 function ModifyOrderPage() {
     const params = Route.useParams();
     const navigate = useNavigate({ from: '/orders/$id/modify' });
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const queryClient = useQueryClient();
     const { form, submitHandler, entity } = useDetailPage({
         pageId,
@@ -84,11 +84,11 @@ function ModifyOrderPage() {
         },
         params: { id: params.id },
         onSuccess: async () => {
-            toast(i18n.t('Successfully updated order'));
+            toast(t`Successfully updated order`);
             form.reset(form.getValues());
         },
         onError: err => {
-            toast(i18n.t('Failed to update order'), {
+            toast(t`Failed to update order`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 17 - 17
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx

@@ -17,7 +17,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
 import { ResultOf } from 'gql.tada';
@@ -54,7 +54,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/draft/$id'
 
 function DraftOrderPage() {
     const params = Route.useParams();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const navigate = useNavigate();
 
     const { entity, refreshEntity, form } = useDetailPage({
@@ -102,7 +102,7 @@ function DraftOrderPage() {
             const order = result.addItemToDraftOrder;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Item added to order'));
+                    toast.success(t`Item added to order`);
                     refreshEntity();
                     break;
                 default:
@@ -118,7 +118,7 @@ function DraftOrderPage() {
             const order = result.adjustDraftOrderLine;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Order line updated'));
+                    toast.success(t`Order line updated`);
                     refreshEntity();
                     break;
                 default:
@@ -134,7 +134,7 @@ function DraftOrderPage() {
             const order = result.removeDraftOrderLine;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Order line removed'));
+                    toast.success(t`Order line removed`);
                     refreshEntity();
                     break;
                 default:
@@ -150,7 +150,7 @@ function DraftOrderPage() {
             const order = result.setCustomerForDraftOrder;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Customer set for order'));
+                    toast.success(t`Customer set for order`);
                     refreshEntity();
                     break;
                 default:
@@ -163,7 +163,7 @@ function DraftOrderPage() {
     const { mutate: setShippingAddressForDraftOrder } = useMutation({
         mutationFn: api.mutate(setShippingAddressForDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof setShippingAddressForDraftOrderDocument>) => {
-            toast.success(i18n.t('Shipping address set for order'));
+            toast.success(t`Shipping address set for order`);
             refreshEntity();
         },
     });
@@ -171,7 +171,7 @@ function DraftOrderPage() {
     const { mutate: setBillingAddressForDraftOrder } = useMutation({
         mutationFn: api.mutate(setBillingAddressForDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof setBillingAddressForDraftOrderDocument>) => {
-            toast.success(i18n.t('Billing address set for order'));
+            toast.success(t`Billing address set for order`);
             refreshEntity();
         },
     });
@@ -179,7 +179,7 @@ function DraftOrderPage() {
     const { mutate: unsetShippingAddressForDraftOrder } = useMutation({
         mutationFn: api.mutate(unsetShippingAddressForDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof unsetShippingAddressForDraftOrderDocument>) => {
-            toast.success(i18n.t('Shipping address unset for order'));
+            toast.success(t`Shipping address unset for order`);
             refreshEntity();
         },
     });
@@ -187,7 +187,7 @@ function DraftOrderPage() {
     const { mutate: unsetBillingAddressForDraftOrder } = useMutation({
         mutationFn: api.mutate(unsetBillingAddressForDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof unsetBillingAddressForDraftOrderDocument>) => {
-            toast.success(i18n.t('Billing address unset for order'));
+            toast.success(t`Billing address unset for order`);
             refreshEntity();
         },
     });
@@ -198,7 +198,7 @@ function DraftOrderPage() {
             const order = result.setDraftOrderShippingMethod;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Shipping method set for order'));
+                    toast.success(t`Shipping method set for order`);
                     refreshEntity();
                     break;
                 default:
@@ -214,7 +214,7 @@ function DraftOrderPage() {
             const order = result.applyCouponCodeToDraftOrder;
             switch (order.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Coupon code set for order'));
+                    toast.success(t`Coupon code set for order`);
                     refreshEntity();
                     break;
                 default:
@@ -227,7 +227,7 @@ function DraftOrderPage() {
     const { mutate: removeCouponCodeForDraftOrder } = useMutation({
         mutationFn: api.mutate(removeCouponCodeFromDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof removeCouponCodeFromDraftOrderDocument>) => {
-            toast.success(i18n.t('Coupon code removed from order'));
+            toast.success(t`Coupon code removed from order`);
             refreshEntity();
         },
     });
@@ -238,7 +238,7 @@ function DraftOrderPage() {
             const order = result.transitionOrderToState;
             switch (order?.__typename) {
                 case 'Order':
-                    toast.success(i18n.t('Draft order completed'));
+                    toast.success(t`Draft order completed`);
                     refreshEntity();
                     setTimeout(() => {
                         navigate({ to: `/orders/$id`, params: { id: order.id } });
@@ -255,7 +255,7 @@ function DraftOrderPage() {
         mutationFn: api.mutate(deleteDraftOrderDocument),
         onSuccess: (result: ResultOf<typeof deleteDraftOrderDocument>) => {
             if (result.deleteDraftOrder.result === 'DELETED') {
-                toast.success(i18n.t('Draft order deleted'));
+                toast.success(t`Draft order deleted`);
                 navigate({ to: '/orders' });
             } else {
                 toast.error(result.deleteDraftOrder.message);
@@ -283,8 +283,8 @@ function DraftOrderPage() {
                 <PageActionBarRight>
                     <PermissionGuard requires={['DeleteOrder']}>
                         <ConfirmationDialog
-                            title={i18n.t('Delete draft order')}
-                            description={i18n.t('Are you sure you want to delete this draft order?')}
+                            title={t`Delete draft order`}
+                            description={t`Are you sure you want to delete this draft order?`}
                             onConfirm={() => {
                                 deleteDraftOrder({ orderId: entity.id });
                             }}

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx

@@ -1,7 +1,7 @@
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { getDetailQueryOptions } from '@/vdb/framework/page/use-detail-page.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { redirect } from '@tanstack/react-router';
 import { OrderDetail } from '../components/order-detail-shared.js';
 import { orderDetailDocument } from '../orders.graphql.js';

+ 5 - 6
packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx

@@ -4,7 +4,7 @@ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
 import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import {
@@ -20,12 +20,13 @@ export const Route = createFileRoute('/_authenticated/_payment-methods/payment-m
 });
 
 function PaymentMethodListPage() {
+    const { t } = useLingui();
     return (
         <ListPage
             pageId="payment-method-list"
             listQuery={paymentMethodListQuery}
             route={Route}
-            title="Payment Methods"
+            title={<Trans>Payment Methods</Trans>}
             defaultVisibility={{
                 name: true,
                 code: true,
@@ -38,7 +39,7 @@ function PaymentMethodListPage() {
             }}
             facetedFilters={{
                 enabled: {
-                    title: 'Enabled',
+                    title: t`Enabled`,
                     options: [
                         { label: 'Enabled', value: true },
                         { label: 'Disabled', value: false },
@@ -47,11 +48,9 @@ function PaymentMethodListPage() {
             }}
             customizeColumns={{
                 name: {
-                    header: 'Name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
                 enabled: {
-                    header: 'Enabled',
                     cell: ({ row }) => <BooleanDisplayBadge value={row.original.enabled} />,
                 },
             }}
@@ -75,7 +74,7 @@ function PaymentMethodListPage() {
                     <Button asChild>
                         <Link to="./new">
                             <PlusIcon className="mr-2 h-4 w-4" />
-                            New Payment Method
+                            <Trans>New Payment Method</Trans>
                         </Link>
                     </Button>
                 </PermissionGuard>

+ 13 - 6
packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx

@@ -19,7 +19,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { PaymentEligibilityCheckerSelector } from './components/payment-eligibility-checker-selector.js';
@@ -51,7 +51,7 @@ function PaymentMethodDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
         pageId,
@@ -95,16 +95,23 @@ function PaymentMethodDetailPage() {
         },
         params: { id: params.id },
         onSuccess: async data => {
-            toast.success(i18n.t(creatingNewEntity ? 'Successfully created payment method' : 'Successfully updated payment method'));
+            toast.success(
+                creatingNewEntity
+                    ? t`Successfully created payment method`
+                    : t`Successfully updated payment method`,
+            );
             resetForm();
             if (creatingNewEntity) {
                 await navigate({ to: `../$id`, params: { id: data.id } });
             }
         },
         onError: err => {
-            toast.error(i18n.t(creatingNewEntity ? 'Failed to create payment method' : 'Failed to update payment method'), {
-                description: err instanceof Error ? err.message : 'Unknown error',
-            });
+            toast.error(
+                creatingNewEntity ? t`Failed to create payment method` : t`Failed to update payment method`,
+                {
+                    description: err instanceof Error ? err.message : 'Unknown error',
+                },
+            );
         },
     });
 

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx

@@ -9,7 +9,7 @@ import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from
 import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
 
 import { AssignFacetValuesDialog } from '../../_products/components/assign-facet-values-dialog.js';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx

@@ -2,7 +2,7 @@ import { Money } from '@/vdb/components/data-display/money.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { useQuery } from '@tanstack/react-query';
 import { useEffect, useState } from 'react';
 

+ 1 - 2
packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants.tsx

@@ -3,7 +3,7 @@ import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js'
 import { StockLevelLabel } from '@/vdb/components/shared/stock-level-label.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { createFileRoute } from '@tanstack/react-router';
 import {
     AssignFacetValuesToProductVariantsBulkAction,
@@ -45,7 +45,6 @@ function ProductListPage() {
             ]}
             customizeColumns={{
                 name: {
-                    header: 'Product Name',
                     cell: ({ row: { original } }) => (
                         <DetailPageButton id={original.id} label={original.name} />
                     ),

+ 13 - 6
packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx

@@ -25,7 +25,7 @@ import {
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { Fragment } from 'react/jsx-runtime';
 import { toast } from 'sonner';
@@ -61,7 +61,7 @@ function ProductVariantDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const { activeChannel } = useChannel();
 
     const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
@@ -97,16 +97,23 @@ function ProductVariantDetailPage() {
         },
         params: { id: params.id },
         onSuccess: data => {
-            toast.success(i18n.t(creatingNewEntity ? 'Successfully created product variant' : 'Successfully updated product variant'));
+            toast.success(
+                creatingNewEntity
+                    ? t`Successfully created product variant`
+                    : t`Successfully updated product variant`,
+            );
             resetForm();
             if (creatingNewEntity) {
                 navigate({ to: `../${(data as any)?.[0]?.id}`, from: Route.id });
             }
         },
         onError: err => {
-            toast.error(i18n.t(creatingNewEntity ? 'Failed to create product variant' : 'Failed to update product variant'), {
-                description: err instanceof Error ? err.message : 'Unknown error',
-            });
+            toast.error(
+                creatingNewEntity ? t`Failed to create product variant` : t`Failed to update product variant`,
+                {
+                    description: err instanceof Error ? err.message : 'Unknown error',
+                },
+            );
         },
     });
 

+ 5 - 5
packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx

@@ -9,8 +9,8 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { Form } from '@/vdb/components/ui/form.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { Plus, Save } from 'lucide-react';
 import { useState } from 'react';
@@ -27,7 +27,7 @@ export function AddOptionGroupDialog({
     onSuccess?: () => void;
 }>) {
     const [open, setOpen] = useState(false);
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const form = useForm<OptionGroup>({
         resolver: zodResolver(optionGroupSchema),
@@ -79,12 +79,12 @@ export function AddOptionGroupDialog({
                 });
             }
 
-            toast.success(i18n.t('Successfully created option group'));
+            toast.success(t`Successfully created option group`);
             setOpen(false);
             onSuccess?.();
         } catch (error) {
-            toast.error(i18n.t('Failed to create option group'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
+            toast.error(t`Failed to create option group`, {
+                description: error instanceof Error ? error.message : t`Unknown error`,
             });
         }
     };

+ 5 - 5
packages/dashboard/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx

@@ -14,8 +14,8 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql, ResultOf, VariablesOf } from '@/vdb/graphql/graphql.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { Plus } from 'lucide-react';
 import { useCallback, useEffect, useState } from 'react';
@@ -83,7 +83,7 @@ export function AddProductVariantDialog({
 }) {
     const [open, setOpen] = useState(false);
     const { activeChannel } = useChannel();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [duplicateVariantError, setDuplicateVariantError] = useState<string | null>(null);
     const [nameTouched, setNameTouched] = useState(false);
 
@@ -166,13 +166,13 @@ export function AddProductVariantDialog({
     const createProductVariantMutation = useMutation({
         mutationFn: api.mutate(createProductVariantDocument),
         onSuccess: (result: ResultOf<typeof createProductVariantDocument>) => {
-            toast.success(i18n.t('Successfully created product variant'));
+            toast.success(t`Successfully created product variant`);
             setOpen(false);
             onSuccess?.();
         },
         onError: error => {
-            toast.error(i18n.t('Failed to create product variant'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
+            toast.error(t`Failed to create product variant`, {
+                description: error instanceof Error ? error.message : t`Unknown error`,
             });
         },
     });

+ 5 - 4
packages/dashboard/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx

@@ -14,7 +14,7 @@ import {
     DialogTitle,
 } from '@/vdb/components/ui/dialog.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 
 import { getDetailQueryOptions } from '@/vdb/framework/page/use-detail-page.js';
 
@@ -55,11 +55,12 @@ export function AssignFacetValuesDialog({
     detailDocument,
     onSuccess,
 }: AssignFacetValuesDialogProps) {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [selectedValues, setSelectedValues] = useState<FacetValue[]>([]);
     const [facetValuesRemoved, setFacetValuesRemoved] = useState(false);
     const [removedFacetValues, setRemovedFacetValues] = useState<Set<string>>(new Set());
     const queryClient = useQueryClient();
+    const entityIdsLength = entityIds.length;
 
     // Fetch existing facet values for the entities
     const { data: entitiesData, isLoading } = useQuery({
@@ -71,7 +72,7 @@ export function AssignFacetValuesDialog({
     const { mutate, isPending } = useMutation({
         mutationFn,
         onSuccess: () => {
-            toast.success(i18n.t(`Successfully updated facet values for ${entityIds.length} ${entityType}`));
+            toast.success(t`Successfully updated facet values for ${entityIdsLength} ${entityType}`);
             onSuccess?.();
             onOpenChange(false);
             // Reset state
@@ -84,7 +85,7 @@ export function AssignFacetValuesDialog({
             });
         },
         onError: () => {
-            toast.error(`Failed to update facet values for ${entityIds.length} ${entityType}`);
+            toast.error(`Failed to update facet values for ${entityIdsLength} ${entityType}`);
         },
     });
 

+ 9 - 12
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx

@@ -12,8 +12,8 @@ import { Form } from '@/vdb/components/ui/form.js';
 import { Input } from '@/vdb/components/ui/input.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { Plus, Trash2 } from 'lucide-react';
 import { useState } from 'react';
@@ -90,7 +90,7 @@ export function CreateProductOptionsDialog({
     onSuccess?: () => void;
 }) {
     const [open, setOpen] = useState(false);
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const { data: productData } = useQuery({
         queryKey: ['product', productId],
@@ -116,13 +116,13 @@ export function CreateProductOptionsDialog({
     const updateProductVariantMutation = useMutation({
         mutationFn: api.mutate(updateProductVariantDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Successfully created product options'));
+            toast.success(t`Successfully created product options`);
             setOpen(false);
             onSuccess?.();
         },
         onError: error => {
-            toast.error(i18n.t('Failed to create product options'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
+            toast.error(t`Failed to create product options`, {
+                description: error instanceof Error ? error.message : t`Unknown error`,
             });
         },
     });
@@ -206,8 +206,8 @@ export function CreateProductOptionsDialog({
                 });
             }
         } catch (error) {
-            toast.error(i18n.t('Failed to create product options'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
+            toast.error(t`Failed to create product options`, {
+                description: error instanceof Error ? error.message : t`Unknown error`,
             });
         }
     };
@@ -266,7 +266,7 @@ export function CreateProductOptionsDialog({
                                             name={`optionGroups.${groupIndex}.name`}
                                             label={<Trans>Option group name</Trans>}
                                             render={({ field }) => (
-                                                <Input {...field} placeholder={i18n.t('e.g. Size')} />
+                                                <Input {...field} placeholder={t`e.g. Size`} />
                                             )}
                                         />
                                         {groupIndex > 0 && (
@@ -287,10 +287,7 @@ export function CreateProductOptionsDialog({
                                                     name={`optionGroups.${groupIndex}.options.${optionIndex}`}
                                                     label={<Trans>Option name</Trans>}
                                                     render={({ field }) => (
-                                                        <Input
-                                                            {...field}
-                                                            placeholder={i18n.t('e.g. Small')}
-                                                        />
+                                                        <Input {...field} placeholder={t`e.g. Small`} />
                                                     )}
                                                 />
                                                 {optionIndex > 0 && (

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx

@@ -10,7 +10,7 @@ import {
 } from '@/vdb/components/ui/dialog.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { normalizeString } from '@/vdb/lib/utils.js';
 import { useMutation } from '@tanstack/react-query';
 import { Plus } from 'lucide-react';

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants.tsx

@@ -5,7 +5,7 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/vdb/components/ui/table.js';
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { useQuery } from '@tanstack/react-query';
 import { useEffect, useMemo, useState } from 'react';
@@ -83,9 +83,9 @@ interface CreateProductVariantsProps {
 }
 
 export function CreateProductVariants({
-    currencyCode = 'USD',
-    onChange,
-}: Readonly<CreateProductVariantsProps>) {
+                                          currencyCode = 'USD',
+                                          onChange,
+                                      }: Readonly<CreateProductVariantsProps>) {
     const { data: stockLocationsResult } = useQuery({
         queryKey: ['stockLocations'],
         queryFn: () => api.query(getStockLocationsDocument, { options: { take: 100 } }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_products/components/option-groups-editor.tsx

@@ -2,7 +2,7 @@ import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js'
 import { Button } from '@/vdb/components/ui/button.js';
 import { Form } from '@/vdb/components/ui/form.js';
 import { Input } from '@/vdb/components/ui/input.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { Plus, Trash2 } from 'lucide-react';
 import { useEffect } from 'react';

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx

@@ -9,7 +9,7 @@ import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from
 import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
 import { api } from '@/vdb/graphql/api.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
 import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
 import {

+ 3 - 3
packages/dashboard/src/app/routes/_authenticated/_products/components/product-option-select.tsx

@@ -8,8 +8,8 @@ import {
     CommandItem,
 } from '@/vdb/components/ui/command.js';
 import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { cn } from '@/vdb/lib/utils.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { Check, ChevronsUpDown, Plus } from 'lucide-react';
 import { useState } from 'react';
 
@@ -41,7 +41,7 @@ export function ProductOptionSelect({
 }: Readonly<ProductOptionSelectProps>) {
     const [open, setOpen] = useState(false);
     const [newOptionInput, setNewOptionInput] = useState('');
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     return (
         <FormFieldWrapper
@@ -63,7 +63,7 @@ export function ProductOptionSelect({
                     <PopoverContent className="w-full p-0">
                         <Command>
                             <CommandInput
-                                placeholder={i18n.t('Search {name}...').replace('{name}', group.name)}
+                                placeholder={t`Search {name}...`.replace('{name}', group.name)}
                                 onValueChange={setNewOptionInput}
                             />
                             <CommandEmpty className="py-2">

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_products/components/product-options-table.tsx

@@ -3,7 +3,7 @@ import { PaginatedListDataTable } from '@/vdb/components/shared/paginated-list-d
 import { Button } from '@/vdb/components/ui/button.js';
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans } from '@/vdb/lib/trans.js';
+import { Trans } from '@lingui/react/macro';
 import { Link } from '@tanstack/react-router';
 import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
 import { PlusIcon } from 'lucide-react';

+ 1 - 0
packages/dashboard/src/app/routes/_authenticated/_products/products.graphql.ts

@@ -15,6 +15,7 @@ export const productListDocument = graphql(`
                 name
                 slug
                 enabled
+                description
             }
             totalItems
         }

+ 5 - 6
packages/dashboard/src/app/routes/_authenticated/_products/products.tsx

@@ -4,7 +4,7 @@ import { Button } from '@/vdb/components/ui/button.js';
 import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
 import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { api } from '@/vdb/graphql/api.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
 import { createFileRoute, Link } from '@tanstack/react-router';
 import { PlusIcon, RefreshCwIcon } from 'lucide-react';
@@ -24,14 +24,14 @@ export const Route = createFileRoute('/_authenticated/_products/products')({
 });
 
 function ProductListPage() {
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const reindexMutation = useMutation({
         mutationFn: () => api.mutate(reindexDocument, {}),
         onSuccess: () => {
-            toast.success(i18n.t('Search index rebuild started'));
+            toast.success(t`Search index rebuild started`);
         },
         onError: () => {
-            toast.error(i18n.t('Search index rebuild could not be started'));
+            toast.error(t`Search index rebuild could not be started`);
         },
     });
 
@@ -43,10 +43,9 @@ function ProductListPage() {
         <ListPage
             pageId="product-list"
             listQuery={productListDocument}
-            title="Products"
+            title={<Trans>Products</Trans>}
             customizeColumns={{
                 name: {
-                    header: 'Product Name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
             }}

+ 4 - 4
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx

@@ -23,7 +23,7 @@ import {
 } from '@/vdb/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
 import { PlusIcon } from 'lucide-react';
 import { useRef } from 'react';
@@ -54,7 +54,7 @@ function ProductDetailPage() {
     const params = Route.useParams();
     const navigate = useNavigate();
     const creatingNewEntity = params.id === NEW_ENTITY_PATH;
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const refreshRef = useRef<() => void>(() => {});
 
     const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
@@ -84,7 +84,7 @@ function ProductDetailPage() {
         params: { id: params.id },
         onSuccess: async data => {
             toast.success(
-                i18n.t(creatingNewEntity ? 'Successfully created product' : 'Successfully updated product'),
+                creatingNewEntity ? t`Successfully created product` : t`Successfully updated product`,
             );
             resetForm();
             if (creatingNewEntity) {
@@ -92,7 +92,7 @@ function ProductDetailPage() {
             }
         },
         onError: err => {
-            toast.error(i18n.t(creatingNewEntity ? 'Failed to create product' : 'Failed to update product'), {
+            toast.error(creatingNewEntity ? t`Failed to create product` : t`Failed to update product`, {
                 description: err instanceof Error ? err.message : 'Unknown error',
             });
         },

+ 11 - 11
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx

@@ -17,8 +17,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
 import { Page, PageBlock, PageLayout, PageTitle } from '@/vdb/framework/layout-engine/page-layout.js';
 import { api } from '@/vdb/graphql/api.js';
 import { ResultOf } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
 import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation, useQuery } from '@tanstack/react-query';
 import { createFileRoute } from '@tanstack/react-router';
 import { Plus, Save, Trash2 } from 'lucide-react';
@@ -82,7 +82,7 @@ function AddOptionValueDialog({
     onSuccess?: () => void;
 }>) {
     const [open, setOpen] = useState(false);
-    const { i18n } = useLingui();
+    const { t } = useLingui();
 
     const form = useForm<AddOptionValueFormValues>({
         resolver: zodResolver(addOptionValueSchema),
@@ -94,14 +94,14 @@ function AddOptionValueDialog({
     const createOptionMutation = useMutation({
         mutationFn: api.mutate(createProductOptionDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Successfully added option value'));
+            toast.success(t`Successfully added option value`);
             setOpen(false);
             form.reset();
             onSuccess?.();
         },
         onError: error => {
-            toast.error(i18n.t('Failed to add option value'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
+            toast.error(t`Failed to add option value`, {
+                description: error instanceof Error ? error.message : t`Unknown error`,
             });
         },
     });
@@ -141,7 +141,7 @@ function AddOptionValueDialog({
                             name="name"
                             label={<Trans>Option value name</Trans>}
                             render={({ field }) => (
-                                <Input {...field} placeholder={i18n.t('e.g., Red, Large, Cotton')} />
+                                <Input {...field} placeholder={t`e.g., Red, Large, Cotton`} />
                             )}
                         />
                         <DialogFooter>
@@ -158,7 +158,7 @@ function AddOptionValueDialog({
 
 function ManageProductVariants() {
     const { id } = Route.useParams();
-    const { i18n } = useLingui();
+    const { t } = useLingui();
     const [optionsToAddToVariant, setOptionsToAddToVariant] = useState<
         Record<string, Record<string, string>>
     >({});
@@ -171,7 +171,7 @@ function ManageProductVariants() {
     const updateVariantMutation = useMutation({
         mutationFn: api.mutate(updateProductVariantDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Variant updated successfully'));
+            toast.success(t`Variant updated successfully`);
             refetch();
         },
     });
@@ -179,7 +179,7 @@ function ManageProductVariants() {
     const deleteVariantMutation = useMutation({
         mutationFn: api.mutate(deleteProductVariantDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Variant deleted successfully'));
+            toast.success(t`Variant deleted successfully`);
             refetch();
         },
     });
@@ -187,7 +187,7 @@ function ManageProductVariants() {
     const removeOptionGroupMutation = useMutation({
         mutationFn: api.mutate(removeOptionGroupFromProductDocument),
         onSuccess: () => {
-            toast.success(i18n.t('Option group removed'));
+            toast.success(t`Option group removed`);
             refetch();
         },
     });
@@ -233,7 +233,7 @@ function ManageProductVariants() {
     };
 
     const deleteVariant = async (variant: Variant) => {
-        if (confirm(i18n.t('Are you sure you want to delete this variant?'))) {
+        if (confirm(t`Are you sure you want to delete this variant?`)) {
             await deleteVariantMutation.mutateAsync({ id: variant.id });
         }
     };

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels