From e4de9837bbf07097206ac3f72c9f3667ece875a0 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari Date: Wed, 11 Dec 2024 12:41:23 +0100 Subject: [PATCH 01/33] feat: add i18n helper and provider --- packages/app-elements/package.json | 4 + packages/app-elements/src/helpers/i18n.ts | 35 ++++++++ packages/app-elements/src/main.ts | 2 + .../src/providers/I18NProvider.tsx | 49 +++++++++++ packages/docs/package.json | 3 +- .../src/stories/examples/I18n.stories.tsx | 81 ++++++++++++++++++ pnpm-lock.yaml | 84 ++++++++++++++++++- 7 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 packages/app-elements/src/helpers/i18n.ts create mode 100644 packages/app-elements/src/providers/I18NProvider.tsx create mode 100644 packages/docs/src/stories/examples/I18n.stories.tsx diff --git a/packages/app-elements/package.json b/packages/app-elements/package.json index 21f6f9fe..73ec8a71 100644 --- a/packages/app-elements/package.json +++ b/packages/app-elements/package.json @@ -45,6 +45,9 @@ "@types/react-datepicker": "^7.0.0", "@types/react-dom": "^18.3.1", "classnames": "^2.5.1", + "i18next": "^24.0.5", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^3.0.1", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", @@ -55,6 +58,7 @@ "react-datepicker": "^7.5.0", "react-dom": "^18.3.1", "react-hook-form": "^7.53.2", + "react-i18next": "^15.1.3", "react-select": "^5.8.3", "react-tooltip": "^5.28.0", "swr": "^2.2.5", diff --git a/packages/app-elements/src/helpers/i18n.ts b/packages/app-elements/src/helpers/i18n.ts new file mode 100644 index 00000000..1c92bef3 --- /dev/null +++ b/packages/app-elements/src/helpers/i18n.ts @@ -0,0 +1,35 @@ +import i18n, { type i18n as I18nInstance } from 'i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import HttpApi, { type HttpBackendOptions } from 'i18next-http-backend' +import { initReactI18next } from 'react-i18next' + +export const languages = ['en', 'it'] as const + +export type I18NLocale = (typeof languages)[number] + +export const initI18n = async ( + localeCode: I18NLocale, + localeUrl?: string +): Promise => { + await i18n + .use(HttpApi) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + load: 'languageOnly', + supportedLngs: languages, + lng: localeCode, + fallbackLng: languages[0], + preload: ['en'], + react: { + useSuspense: true + }, + backend: { + loadPath: + // TODO: Define the path to the i18n public files + localeUrl ?? 'https://cdn.commercelayer.io/i18n/{{lng}}.json' + }, + debug: true + }) + return i18n +} diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index 95880a50..2aab09a4 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -21,6 +21,7 @@ export { timeSeparator } from '#helpers/date' export { downloadJsonAsFile } from '#helpers/downloadJsonAsFile' +export { initI18n, languages, type I18NLocale } from '#helpers/i18n' export { computeFullname, formatDisplayName } from '#helpers/name' export { formatResourceName, @@ -61,6 +62,7 @@ export { export { createApp, type ClAppKey, type ClAppProps } from '#providers/createApp' export { ErrorBoundary } from '#providers/ErrorBoundary' export { GTMProvider, useTagManager } from '#providers/GTMProvider' +export { I18NProvider } from '#providers/I18NProvider' export { MetaTags, TokenProvider, diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx new file mode 100644 index 00000000..9526402c --- /dev/null +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -0,0 +1,49 @@ +import { type I18NLocale, initI18n, languages } from '#helpers/i18n' +import { type i18n as I18nInstance } from 'i18next' +import React, { type ReactNode, useEffect, useState } from 'react' +import { I18nextProvider } from 'react-i18next' + +interface I18NProviderProps { + localeCode?: I18NLocale + localeUrl?: string + children: ReactNode +} + +export const I18NProvider: React.FC = ({ + localeCode = languages[0], + localeUrl, + children +}) => { + const [i18nInstance, setI18nInstance] = useState() + + useEffect(() => { + const setupI18n = async (): Promise => { + try { + const instance = await initI18n(localeCode, localeUrl) + console.log('I18n', instance) + if (instance.isInitialized) { + console.log('I18n translation:', instance.t('common.all')) + setI18nInstance(instance) + } + } catch (error) { + console.error('Error initializing i18n:', error) + } + } + + if ( + i18nInstance == null || + (i18nInstance != null && !i18nInstance.isInitialized) + ) { + void setupI18n() + } + }, [localeCode, localeUrl, i18nInstance]) + + if ( + i18nInstance == null || + (i18nInstance != null && !i18nInstance.isInitialized) + ) { + return
Loading i18n
+ } + + return {children} +} diff --git a/packages/docs/package.json b/packages/docs/package.json index e49a29c6..5cf2bb7a 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -19,7 +19,8 @@ "dependencies": { "prettier": "^3.4.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-i18next": "^15.1.3" }, "devDependencies": { "@babel/core": "^7.26.0", diff --git a/packages/docs/src/stories/examples/I18n.stories.tsx b/packages/docs/src/stories/examples/I18n.stories.tsx new file mode 100644 index 00000000..5bb1c5f8 --- /dev/null +++ b/packages/docs/src/stories/examples/I18n.stories.tsx @@ -0,0 +1,81 @@ +import { I18NProvider } from '#providers/I18NProvider' +import { Spacer } from '#ui/atoms/Spacer' +import { Text } from '#ui/atoms/Text' +import { PageLayout } from '#ui/composite/PageLayout' +import { + InputSelect, + isSingleValueSelected, + type InputSelectValue +} from '#ui/forms/InputSelect' +import { type PossibleSelectValue } from '#ui/forms/InputSelect/InputSelect' +import { Description, Primary, Subtitle, Title } from '@storybook/blocks' +import { type Meta, type StoryFn } from '@storybook/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +const setup: Meta = { + title: 'Examples/I18N', + parameters: { + layout: 'fullscreen', + docs: { + page: () => ( + <> + + + <Subtitle /> + <Description /> + <Primary /> + </I18NProvider> + </> + ) + } + } +} + +export const Default: StoryFn = (): JSX.Element => { + const languages = [ + { value: 'en', label: 'English' }, + { value: 'it', label: 'Italiano' } + ] + + const { t, i18n } = useTranslation() + const [activeLang, setActiveLang] = useState< + InputSelectValue | PossibleSelectValue + >( + languages.find( + (lang) => lang.value === i18n.language + ) as PossibleSelectValue + ) + + return ( + <PageLayout title='Translations'> + <InputSelect + label='Languages' + initialValues={languages} + value={activeLang as InputSelectValue} + onSelect={(value) => { + if (isSingleValueSelected(value)) { + setActiveLang(value) + void i18n.changeLanguage(value.value as string) + } + }} + /> + <Spacer top='4'> + <Text> + Translation of string <strong>common.all</strong>: {t('common.all')} + </Text> + </Spacer> + <Spacer top='4'> + <Text> + Translation of string <strong>common.all_female</strong>:{' '} + {t('common.all_female')} + </Text> + </Spacer> + </PageLayout> + ) +} + +export default setup diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe207ba5..5417de19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,15 @@ importers: classnames: specifier: ^2.5.1 version: 2.5.1 + i18next: + specifier: ^24.0.5 + version: 24.0.5(typescript@5.7.2) + i18next-browser-languagedetector: + specifier: ^8.0.0 + version: 8.0.0 + i18next-http-backend: + specifier: ^3.0.1 + version: 3.0.1(encoding@0.1.13) js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -74,6 +83,9 @@ importers: react-hook-form: specifier: ^7.53.2 version: 7.53.2(react@18.3.1) + react-i18next: + specifier: ^15.1.3 + version: 15.1.3(i18next@24.0.5(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-select: specifier: ^5.8.3 version: 5.8.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -183,6 +195,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-i18next: + specifier: ^15.1.3 + version: 15.1.3(i18next@24.0.5(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -311,6 +326,8 @@ importers: specifier: ^3.23.8 version: 3.23.8 + packages/i18n-locales: {} + packages: '@adobe/css-tools@4.4.1': @@ -3720,6 +3737,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -3744,6 +3764,20 @@ packages: engines: {node: '>=18'} hasBin: true + i18next-browser-languagedetector@8.0.0: + resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} + + i18next-http-backend@3.0.1: + resolution: {integrity: sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==} + + i18next@24.0.5: + resolution: {integrity: sha512-1jSdEzgFPGLZRsQwydoMFCBBaV+PmrVEO5WhANllZPX4y2JSGTxUjJ+xVklHIsiS95uR8gYc/y0hYZWevucNjg==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -5237,6 +5271,19 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-i18next@15.1.3: + resolution: {integrity: sha512-J11oA30FbM3NZegUZjn8ySK903z6PLBz/ZuBYyT1JMR0QPrW6PFXvl1WoUhortdGi9dM0m48/zJQlPskVZXgVw==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6229,6 +6276,10 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} @@ -6480,7 +6531,7 @@ snapshots: debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 6.3.1 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -10571,6 +10622,10 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + http-cache-semantics@4.1.1: {} http-proxy-agent@7.0.2: @@ -10593,6 +10648,22 @@ snapshots: husky@9.1.7: {} + i18next-browser-languagedetector@8.0.0: + dependencies: + '@babel/runtime': 7.26.0 + + i18next-http-backend@3.0.1(encoding@0.1.13): + dependencies: + cross-fetch: 4.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + i18next@24.0.5(typescript@5.7.2): + dependencies: + '@babel/runtime': 7.26.0 + optionalDependencies: + typescript: 5.7.2 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -12422,6 +12493,15 @@ snapshots: dependencies: react: 18.3.1 + react-i18next@15.1.3(i18next@24.0.5(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + html-parse-stringify: 3.0.1 + i18next: 24.0.5(typescript@5.7.2) + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-is@16.13.1: {} react-is@17.0.2: {} @@ -13518,6 +13598,8 @@ snapshots: - tsx - yaml + void-elements@3.1.0: {} + vscode-uri@3.0.8: {} w3c-xmlserializer@5.0.0: From 2351b3f1696deb31e0a0fa4ad268c7ec89484001 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 11 Dec 2024 12:43:08 +0100 Subject: [PATCH 02/33] feat: add i18n-locales package --- packages/i18n-locales/package.json | 5 +++++ packages/i18n-locales/public/en.json | 6 ++++++ packages/i18n-locales/public/it.json | 6 ++++++ 3 files changed, 17 insertions(+) create mode 100644 packages/i18n-locales/package.json create mode 100644 packages/i18n-locales/public/en.json create mode 100644 packages/i18n-locales/public/it.json diff --git a/packages/i18n-locales/package.json b/packages/i18n-locales/package.json new file mode 100644 index 00000000..8f914022 --- /dev/null +++ b/packages/i18n-locales/package.json @@ -0,0 +1,5 @@ +{ + "scripts": { + "serve": "pnpm dlx serve --cors public" + } +} \ No newline at end of file diff --git a/packages/i18n-locales/public/en.json b/packages/i18n-locales/public/en.json new file mode 100644 index 00000000..7b83c898 --- /dev/null +++ b/packages/i18n-locales/public/en.json @@ -0,0 +1,6 @@ +{ + "common": { + "all": "all", + "all_female": "all" + } +} \ No newline at end of file diff --git a/packages/i18n-locales/public/it.json b/packages/i18n-locales/public/it.json new file mode 100644 index 00000000..c36d1b69 --- /dev/null +++ b/packages/i18n-locales/public/it.json @@ -0,0 +1,6 @@ +{ + "common": { + "all": "tutti", + "all_female": "tutte" + } +} \ No newline at end of file From 39f57fe57f4148dbe9ff5d97fd2cfa511ca5d89d Mon Sep 17 00:00:00 2001 From: Marco Montalbano <marco.montalbano@commercelayer.io> Date: Wed, 11 Dec 2024 16:06:35 +0100 Subject: [PATCH 03/33] chore: export useTranslation --- packages/app-elements/src/main.ts | 3 ++- packages/app-elements/src/providers/I18NProvider.tsx | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index 2aab09a4..02ea284f 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -62,7 +62,7 @@ export { export { createApp, type ClAppKey, type ClAppProps } from '#providers/createApp' export { ErrorBoundary } from '#providers/ErrorBoundary' export { GTMProvider, useTagManager } from '#providers/GTMProvider' -export { I18NProvider } from '#providers/I18NProvider' +export { I18NProvider, useTranslation } from '#providers/I18NProvider' export { MetaTags, TokenProvider, @@ -387,3 +387,4 @@ export { getStockTransferDisplayStatus, getStockTransferStatusName } from '#dictionaries/stockTransfers' + diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx index 9526402c..dfe0f4f8 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -3,6 +3,8 @@ import { type i18n as I18nInstance } from 'i18next' import React, { type ReactNode, useEffect, useState } from 'react' import { I18nextProvider } from 'react-i18next' +export { useTranslation } from 'react-i18next' + interface I18NProviderProps { localeCode?: I18NLocale localeUrl?: string From 4c6d87da36eeb24d782d3aeb118c2cd4e89e3fd8 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 11 Dec 2024 16:44:05 +0100 Subject: [PATCH 04/33] perf: import useTranslation from app-elements in docs --- packages/docs/package.json | 5 ++--- packages/docs/src/stories/examples/I18n.stories.tsx | 3 +-- pnpm-lock.yaml | 5 +---- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/docs/package.json b/packages/docs/package.json index 5cf2bb7a..1b05559e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -19,8 +19,7 @@ "dependencies": { "prettier": "^3.4.1", "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-i18next": "^15.1.3" + "react-dom": "^18.3.1" }, "devDependencies": { "@babel/core": "^7.26.0", @@ -69,4 +68,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/packages/docs/src/stories/examples/I18n.stories.tsx b/packages/docs/src/stories/examples/I18n.stories.tsx index 5bb1c5f8..7c2232e6 100644 --- a/packages/docs/src/stories/examples/I18n.stories.tsx +++ b/packages/docs/src/stories/examples/I18n.stories.tsx @@ -1,4 +1,4 @@ -import { I18NProvider } from '#providers/I18NProvider' +import { I18NProvider, useTranslation } from '#providers/I18NProvider' import { Spacer } from '#ui/atoms/Spacer' import { Text } from '#ui/atoms/Text' import { PageLayout } from '#ui/composite/PageLayout' @@ -11,7 +11,6 @@ import { type PossibleSelectValue } from '#ui/forms/InputSelect/InputSelect' import { Description, Primary, Subtitle, Title } from '@storybook/blocks' import { type Meta, type StoryFn } from '@storybook/react' import { useState } from 'react' -import { useTranslation } from 'react-i18next' const setup: Meta = { title: 'Examples/I18N', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5417de19..55cd2f28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,9 +195,6 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) - react-i18next: - specifier: ^15.1.3 - version: 15.1.3(i18next@24.0.5(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -6531,7 +6528,7 @@ snapshots: debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 7.6.3 + semver: 6.3.1 transitivePeerDependencies: - supports-color From 546d416bd14863cb22bd395213ea86ed703fe396 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 11 Dec 2024 17:06:32 +0100 Subject: [PATCH 05/33] chore: rename localeUrl prop --- packages/app-elements/src/helpers/i18n.ts | 9 +++++---- packages/app-elements/src/main.ts | 1 - packages/app-elements/src/providers/I18NProvider.tsx | 10 ++++------ packages/docs/src/stories/examples/I18n.stories.tsx | 5 +---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/app-elements/src/helpers/i18n.ts b/packages/app-elements/src/helpers/i18n.ts index 1c92bef3..9715829f 100644 --- a/packages/app-elements/src/helpers/i18n.ts +++ b/packages/app-elements/src/helpers/i18n.ts @@ -9,8 +9,11 @@ export type I18NLocale = (typeof languages)[number] export const initI18n = async ( localeCode: I18NLocale, - localeUrl?: string + baseUrl?: string ): Promise<I18nInstance> => { + // TODO: Define the path to the i18n public files + const localeUrl = `${baseUrl ?? 'https://cdn.commercelayer.io/i18n/'}{{lng}}.json` + await i18n .use(HttpApi) .use(LanguageDetector) @@ -25,9 +28,7 @@ export const initI18n = async ( useSuspense: true }, backend: { - loadPath: - // TODO: Define the path to the i18n public files - localeUrl ?? 'https://cdn.commercelayer.io/i18n/{{lng}}.json' + loadPath: localeUrl }, debug: true }) diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index 02ea284f..b4bd912a 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -387,4 +387,3 @@ export { getStockTransferDisplayStatus, getStockTransferStatusName } from '#dictionaries/stockTransfers' - diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx index dfe0f4f8..c52bf0c9 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -7,13 +7,13 @@ export { useTranslation } from 'react-i18next' interface I18NProviderProps { localeCode?: I18NLocale - localeUrl?: string + baseUrl?: string children: ReactNode } export const I18NProvider: React.FC<I18NProviderProps> = ({ localeCode = languages[0], - localeUrl, + baseUrl, children }) => { const [i18nInstance, setI18nInstance] = useState<I18nInstance | undefined>() @@ -21,10 +21,8 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ useEffect(() => { const setupI18n = async (): Promise<void> => { try { - const instance = await initI18n(localeCode, localeUrl) - console.log('I18n', instance) + const instance = await initI18n(localeCode, baseUrl) if (instance.isInitialized) { - console.log('I18n translation:', instance.t('common.all')) setI18nInstance(instance) } } catch (error) { @@ -38,7 +36,7 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ ) { void setupI18n() } - }, [localeCode, localeUrl, i18nInstance]) + }, [localeCode, baseUrl, i18nInstance]) if ( i18nInstance == null || diff --git a/packages/docs/src/stories/examples/I18n.stories.tsx b/packages/docs/src/stories/examples/I18n.stories.tsx index 7c2232e6..38268ff4 100644 --- a/packages/docs/src/stories/examples/I18n.stories.tsx +++ b/packages/docs/src/stories/examples/I18n.stories.tsx @@ -19,10 +19,7 @@ const setup: Meta = { docs: { page: () => ( <> - <I18NProvider - localeCode='en' - localeUrl='http://localhost:3000/{{lng}}.json' - > + <I18NProvider localeCode='en' baseUrl='http://localhost:3000/'> <Title /> <Subtitle /> <Description /> From 584f4bd82e9e5cdb8efca12531ddde6a411f44cd Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Thu, 12 Dec 2024 13:32:16 +0100 Subject: [PATCH 06/33] feat: add translations for dictionaries --- .../src/dictionaries/customers.ts | 23 ++-- .../app-elements/src/dictionaries/orders.ts | 93 +++++++------- .../src/dictionaries/promotions.ts | 12 +- .../app-elements/src/dictionaries/returns.ts | 39 +++--- .../src/dictionaries/shipments.ts | 53 ++++---- .../src/dictionaries/stockTransfers.ts | 41 +++--- packages/i18n-locales/public/en.json | 117 +++++++++++++++++- packages/i18n-locales/public/it.json | 117 +++++++++++++++++- 8 files changed, 376 insertions(+), 119 deletions(-) diff --git a/packages/app-elements/src/dictionaries/customers.ts b/packages/app-elements/src/dictionaries/customers.ts index 31dbc8b1..5882a2b6 100644 --- a/packages/app-elements/src/dictionaries/customers.ts +++ b/packages/app-elements/src/dictionaries/customers.ts @@ -1,3 +1,4 @@ +import { useTranslation } from '#providers/I18NProvider' import type { Customer } from '@commercelayer/sdk' import type { DisplayStatus } from './types' @@ -6,38 +7,42 @@ export interface CustomerDisplayStatus extends DisplayStatus {} export function getCustomerDisplayStatus( customerObj: Customer ): CustomerDisplayStatus { + const { t } = useTranslation() + switch (customerObj.status) { case 'prospect': return { - label: 'Prospect', + label: t('common.resources.customers.status.prospect'), icon: 'chatCircle', color: 'orange', - task: 'Prospect' + task: t('common.resources.customers.status.prospect') } case 'acquired': return { - label: 'Acquired', + label: t('common.resources.customers.status.acquired'), icon: 'check', color: 'orange', - task: 'Acquired' + task: t('common.resources.customers.status.acquired') } case 'repeat': return { - label: 'Repeat', + label: t('common.resources.customers.status.repeat'), icon: 'arrowUpRight', color: 'orange', - task: 'Repeat' + task: t('common.resources.customers.status.repeat') } } } export function getCustomerStatusName(status: Customer['status']): string { + const { t } = useTranslation() + const dictionary: Record<typeof status, string> = { - prospect: 'Prospect', - acquired: 'Acquired', - repeat: 'Repeat' + prospect: t('common.resources.customers.status.prospect'), + acquired: t('common.resources.customers.status.acquired'), + repeat: t('common.resources.customers.status.repeat') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index fa0dfb43..7f8bc31e 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -1,5 +1,7 @@ +import { useTranslation } from '#providers/I18NProvider' import type { StatusIconProps } from '#ui/atoms/StatusIcon' import type { Order } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' export interface OrderDisplayStatus extends DisplayStatus { label: string @@ -9,15 +11,16 @@ export interface OrderDisplayStatus extends DisplayStatus { } export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { + const { t } = useTranslation() const combinedStatus = `${order.status}:${order.payment_status}:${order.fulfillment_status}` as const if (order.status === 'editing') { return { - label: 'Editing', + label: t('common.resources.orders.status.editing'), icon: 'pencilSimple', color: 'orange', - task: 'Editing' + task: t('common.resources.orders.status.editing') } } @@ -31,49 +34,49 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'placed:free:unfulfilled': case 'placed:free:not_required': return { - label: 'Placed', + label: t('common.resources.orders.status.placed'), icon: 'arrowDown', color: 'orange', - task: 'Awaiting approval' + task: t('common.resources.orders.task.awaiting_approval') } case 'placed:unpaid:unfulfilled': return { - label: 'Placed', + label: t('common.resources.orders.status.placed'), icon: 'x', color: 'red', - task: 'Error to cancel' + task: t('common.resources.orders.task.error_to_cancel') } case 'approved:authorized:unfulfilled': case 'approved:authorized:not_required': return { - label: 'Approved', + label: t('common.resources.orders.status.approved'), icon: 'creditCard', color: 'orange', - task: 'Payment to capture' + task: t('common.resources.orders.task.payment_to_capture') } case 'approved:paid:in_progress': case 'approved:partially_refunded:in_progress': return { - label: 'In progress', + label: t('common.resources.orders.status.in_progress'), icon: 'arrowClockwise', color: 'orange', - task: 'Fulfillment in progress' + task: t('common.resources.orders.task.fulfillment_in_progress') } case 'approved:authorized:in_progress': return { - label: 'In progress (Manual)', + label: t('common.resources.orders.status.in_progress_manual'), icon: 'arrowClockwise', color: 'orange', - task: 'Fulfillment in progress' + task: t('common.resources.orders.task.fulfillment_in_progress') } case 'approved:paid:fulfilled': return { - label: 'Fulfilled', + label: t('common.resources.orders.fulfillment_status.fulfilled'), icon: 'check', color: 'green' } @@ -81,7 +84,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { // TODO: This could be a gift-card and what If i do return? case 'approved:free:fulfilled': return { - label: 'Fulfilled', + label: t('common.resources.orders.fulfillment_status.fulfilled'), icon: 'check', color: 'green' } @@ -89,21 +92,21 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'approved:paid:not_required': case 'approved:partially_refunded:not_required': return { - label: 'Approved', + label: t('common.resources.orders.status.approved'), icon: 'check', color: 'green' } case 'approved:free:not_required': return { - label: 'Approved', + label: t('common.resources.orders.status.approved'), icon: 'check', color: 'green' } case 'approved:partially_refunded:fulfilled': return { - label: 'Part. refunded', + label: t('common.resources.orders.payment_status.partially_refunded'), icon: 'check', color: 'green' } @@ -114,14 +117,14 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'cancelled:unpaid:unfulfilled': case 'cancelled:free:unfulfilled': return { - label: 'Cancelled', + label: t('common.resources.orders.status.cancelled'), icon: 'x', color: 'gray' } case 'cancelled:refunded:fulfilled': return { - label: 'Cancelled', + label: t('common.resources.orders.status.cancelled'), icon: 'x', color: 'gray' } @@ -130,14 +133,14 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'pending:authorized:unfulfilled': case 'pending:free:unfulfilled': return { - label: 'Pending', + label: t('common.resources.orders.status.pending'), icon: 'shoppingBag', color: 'white' } default: return { - label: `Not handled: (${combinedStatus})`, + label: `${t('common.resources.common.status.not_handled')}: (${combinedStatus})`, icon: 'warning', color: 'white' } @@ -168,13 +171,13 @@ export function getOrderTransactionName( export function getOrderStatusName(status: Order['status']): string { const dictionary: Record<typeof status, string> = { - approved: 'Approved', - cancelled: 'Cancelled', - draft: 'Draft', - editing: 'Editing', - pending: 'Pending', - placed: 'Placed', - placing: 'Placing' + approved: t('common.resources.orders.status.approved'), + cancelled: t('common.resources.orders.status.cancelled'), + draft: t('common.resources.orders.status.draft'), + editing: t('common.resources.orders.status.editing'), + pending: t('common.resources.orders.status.pending'), + placed: t('common.resources.orders.status.placed'), + placing: t('common.resources.orders.status.placing') } return dictionary[status] @@ -184,16 +187,22 @@ export function getOrderPaymentStatusName( status: Order['payment_status'] ): string { const dictionary: Record<typeof status, string> = { - authorized: 'Authorized', - paid: 'Paid', - unpaid: 'Unpaid', - free: 'Free', - voided: 'Voided', - refunded: 'Refunded', - partially_authorized: 'Part. authorized', - partially_paid: 'Part. paid', - partially_refunded: 'Part. refunded', - partially_voided: 'Part. voided' + authorized: t('common.resources.orders.payment_status.authorized'), + paid: t('common.resources.orders.payment_status.paid'), + unpaid: t('common.resources.orders.payment_status.unpaid'), + free: t('common.resources.orders.payment_status.free'), + voided: t('common.resources.orders.payment_status.voided'), + refunded: t('common.resources.orders.payment_status.refunded'), + partially_authorized: t( + 'common.resources.orders.payment_status.partially_authorized' + ), + partially_paid: t('common.resources.orders.payment_status.partially_paid'), + partially_refunded: t( + 'common.resources.orders.payment_status.partially_refunded' + ), + partially_voided: t( + 'common.resources.orders.payment_status.partially_voided' + ) } return dictionary[status] @@ -203,10 +212,10 @@ export function getOrderFulfillmentStatusName( status: Order['fulfillment_status'] ): string { const dictionary: Record<typeof status, string> = { - unfulfilled: 'Unfulfilled', - in_progress: 'In progress', - fulfilled: 'Fulfilled', - not_required: 'Not required' + unfulfilled: t('common.resources.orders.fulfillment_status.unfulfilled'), + in_progress: t('common.resources.orders.fulfillment_status.in_progress'), + fulfilled: t('common.resources.orders.fulfillment_status.fulfilled'), + not_required: t('common.resources.orders.fulfillment_status.not_required') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/promotions.ts b/packages/app-elements/src/dictionaries/promotions.ts index 12f1a36b..13409504 100644 --- a/packages/app-elements/src/dictionaries/promotions.ts +++ b/packages/app-elements/src/dictionaries/promotions.ts @@ -1,4 +1,5 @@ import { getEventDateInfo } from '#helpers/date' +import { useTranslation } from '#providers/I18NProvider' import type { Promotion } from '@commercelayer/sdk' import type { DisplayStatus } from './types' @@ -9,10 +10,11 @@ interface PromotionDisplayStatus extends DisplayStatus { export function getPromotionDisplayStatus( promotion: Omit<Promotion, 'type' | 'promotion_rules'> ): PromotionDisplayStatus { + const { t } = useTranslation() if (promotion.disabled_at != null) { return { status: 'disabled', - label: 'Disabled', + label: t('common.resources.promotions.status.disabled'), icon: 'minus', color: 'lightGray' } @@ -29,7 +31,7 @@ export function getPromotionDisplayStatus( ) { return { status: 'used', - label: 'Expired', + label: t('common.resources.promotions.status.expired'), icon: 'flag', color: 'gray' } @@ -39,7 +41,7 @@ export function getPromotionDisplayStatus( case 'past': return { status: 'expired', - label: 'Expired', + label: t('common.resources.promotions.status.expired'), icon: 'flag', color: 'gray' } @@ -47,7 +49,7 @@ export function getPromotionDisplayStatus( case 'upcoming': return { status: 'upcoming', - label: 'Upcoming', + label: t('common.resources.promotions.status.upcoming'), icon: 'calendarBlank', color: 'gray' } @@ -55,7 +57,7 @@ export function getPromotionDisplayStatus( case 'active': return { status: 'active', - label: 'Active', + label: t('common.resources.promotions.status.active'), icon: 'pulse', color: 'green' } diff --git a/packages/app-elements/src/dictionaries/returns.ts b/packages/app-elements/src/dictionaries/returns.ts index aa62570c..c9546dee 100644 --- a/packages/app-elements/src/dictionaries/returns.ts +++ b/packages/app-elements/src/dictionaries/returns.ts @@ -1,5 +1,6 @@ import { type StatusIconProps } from '#ui/atoms/StatusIcon' import type { Return } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' export interface ReturnDisplayStatus extends DisplayStatus { @@ -13,59 +14,59 @@ export function getReturnDisplayStatus(returnObj: Return): ReturnDisplayStatus { switch (returnObj.status) { case 'requested': return { - label: 'Requested', + label: t('common.resources.returns.status.requested'), icon: 'chatCircle', color: 'orange', - task: 'Requested' + task: t('common.resources.returns.status.requested') } case 'approved': return { - label: 'Approved', + label: t('common.resources.returns.status.approved'), icon: 'check', color: 'orange', - task: 'Approved' + task: t('common.resources.returns.status.approved') } case 'shipped': return { - label: 'Shipped', + label: t('common.resources.returns.status.shipped'), icon: 'arrowUpRight', color: 'orange', - task: 'Shipped' + task: t('common.resources.returns.status.shipped') } case 'received': return { - label: 'Received', + label: t('common.resources.returns.status.received'), icon: 'check', color: 'green' } case 'cancelled': return { - label: 'Cancelled', + label: t('common.resources.returns.status.cancelled'), icon: 'x', color: 'gray' } case 'rejected': return { - label: 'Rejected', + label: t('common.resources.returns.status.rejected'), icon: 'x', color: 'red' } case 'refunded': return { - label: 'Refunded', + label: t('common.resources.returns.status.refunded'), icon: 'creditCard', color: 'green' } default: return { - label: `Not handled: (${returnObj.status})`, + label: `${t('common.resources.common.status.not_handled')}: (${returnObj.status})`, icon: 'warning', color: 'white' } @@ -74,14 +75,14 @@ export function getReturnDisplayStatus(returnObj: Return): ReturnDisplayStatus { export function getReturnStatusName(status: Return['status']): string { const dictionary: Record<typeof status, string> = { - draft: 'Draft', - requested: 'Requested', - approved: 'Approved', - shipped: 'Shipped', - received: 'Received', - cancelled: 'Cancelled', - rejected: 'Rejected', - refunded: 'Refunded' + draft: t('common.resources.returns.status.draft'), + requested: t('common.resources.returns.status.requested'), + approved: t('common.resources.returns.status.approved'), + shipped: t('common.resources.returns.status.shipped'), + received: t('common.resources.returns.status.received'), + cancelled: t('common.resources.returns.status.cancelled'), + rejected: t('common.resources.returns.status.rejected'), + refunded: t('common.resources.returns.status.refunded') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/shipments.ts b/packages/app-elements/src/dictionaries/shipments.ts index 71329208..4093eb94 100644 --- a/packages/app-elements/src/dictionaries/shipments.ts +++ b/packages/app-elements/src/dictionaries/shipments.ts @@ -1,3 +1,4 @@ +import { useTranslation } from '#providers/I18NProvider' import { type StatusIconProps } from '#ui/atoms/StatusIcon' import type { Shipment } from '@commercelayer/sdk' import type { DisplayStatus } from './types' @@ -13,6 +14,8 @@ export function getShipmentDisplayStatus( shipment: Shipment, awaitingStockTransfer: boolean = false ): ShipmentDisplayStatus { + const { t } = useTranslation() + const shipmentStatus = awaitingStockTransfer ? 'awaiting_stock_transfer' : shipment.status @@ -20,74 +23,74 @@ export function getShipmentDisplayStatus( switch (shipmentStatus) { case 'upcoming': return { - label: 'Upcoming', + label: t('common.resources.shipments.status.upcoming'), icon: 'truck', color: 'gray' } case 'cancelled': return { - label: 'Cancelled', + label: t('common.resources.shipments.status.cancelled'), icon: 'x', color: 'gray' } case 'draft': return { - label: 'Draft', + label: t('common.resources.shipments.status.draft'), icon: 'minus', color: 'gray' } case 'on_hold': return { - label: 'On hold', + label: t('common.resources.shipments.status.on_hold'), icon: 'hourglass', color: 'orange', - task: 'On hold' + task: t('common.resources.shipments.status.on_hold') } case 'packing': return { - label: 'Packing', + label: t('common.resources.shipments.status.packing'), icon: 'package', color: 'orange', - task: 'Packing' + task: t('common.resources.shipments.status.packing') } case 'picking': return { - label: 'Picking', + label: t('common.resources.shipments.status.picking'), icon: 'arrowDown', color: 'orange', - task: 'Picking' + task: t('common.resources.shipments.status.picking') } case 'ready_to_ship': return { - label: 'Ready to ship', + label: t('common.resources.shipments.status.ready_to_ship'), icon: 'arrowUpRight', color: 'orange', - task: 'Ready to ship' + task: t('common.resources.shipments.status.ready_to_ship') } case 'shipped': return { - label: 'Shipped', + label: t('common.resources.shipments.status.shipped'), icon: 'arrowUpRight', color: 'green' } case 'delivered': return { - label: 'Delivered', + label: t('common.resources.shipments.status.delivered'), icon: 'check', color: 'green' } case 'awaiting_stock_transfer': return { - label: 'Awaiting stock transfers', + label: t('common.resources.shipments.status.awaiting_stock_transfer'), icon: 'hourglass', color: 'orange', task: 'Awaiting stock transfers' @@ -95,7 +98,7 @@ export function getShipmentDisplayStatus( default: return { - label: `Not handled: (${shipment.status})`, + label: `${t('common.resources.common.status.not_handled')}: (${shipment.status})`, icon: 'warning', color: 'white' } @@ -103,16 +106,18 @@ export function getShipmentDisplayStatus( } export function getShipmentStatusName(status: Shipment['status']): string { + const { t } = useTranslation() + const dictionary: Record<typeof status, string> = { - draft: 'Draft', - on_hold: 'On hold', - upcoming: 'Upcoming', - packing: 'Packing', - picking: 'Picking', - ready_to_ship: 'Ready to ship', - shipped: 'Shipped', - cancelled: 'Cancelled', - delivered: 'Delivered' + draft: t('common.resources.shipments.status.draft'), + on_hold: t('common.resources.shipments.status.on_hold'), + upcoming: t('common.resources.shipments.status.upcoming'), + packing: t('common.resources.shipments.status.packing'), + picking: t('common.resources.shipments.status.picking'), + ready_to_ship: t('common.resources.shipments.status.ready_to_ship'), + shipped: t('common.resources.shipments.status.shipped'), + cancelled: t('common.resources.shipments.status.cancelled'), + delivered: t('common.resources.shipments.status.delivered') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/stockTransfers.ts b/packages/app-elements/src/dictionaries/stockTransfers.ts index dfec0a8a..eb0cf11d 100644 --- a/packages/app-elements/src/dictionaries/stockTransfers.ts +++ b/packages/app-elements/src/dictionaries/stockTransfers.ts @@ -1,3 +1,4 @@ +import { useTranslation } from '#providers/I18NProvider' import { type StatusIconProps } from '#ui/atoms/StatusIcon' import type { StockTransfer } from '@commercelayer/sdk' import type { DisplayStatus } from './types' @@ -12,56 +13,58 @@ export interface StockTransferDisplayStatus extends DisplayStatus { export function getStockTransferDisplayStatus( stockTransfer: StockTransfer ): StockTransferDisplayStatus { + const { t } = useTranslation() + switch (stockTransfer.status) { case 'upcoming': return { - label: 'Upcoming', + label: t('common.resources.stock_transfers.status.upcoming'), icon: 'arrowUpRight', color: 'orange', - task: 'Upcoming' + task: t('common.resources.stock_transfers.status.upcoming') } case 'on_hold': return { - label: 'On hold', + label: t('common.resources.stock_transfers.status.on_hold'), icon: 'hourglass', color: 'orange', - task: 'On hold' + task: t('common.resources.stock_transfers.status.on_hold') } case 'picking': return { - label: 'Picking', + label: t('common.resources.stock_transfers.status.picking'), icon: 'arrowDown', color: 'orange', - task: 'Picking' + task: t('common.resources.stock_transfers.status.picking') } case 'in_transit': return { - label: 'In transit', + label: t('common.resources.stock_transfers.status.in_transit'), icon: 'arrowsLeftRight', color: 'orange', - task: 'In transit' + task: t('common.resources.stock_transfers.status.in_transit') } case 'completed': return { - label: 'Completed', + label: t('common.resources.stock_transfers.status.completed'), icon: 'check', color: 'green' } case 'cancelled': return { - label: 'Cancelled', + label: t('common.resources.stock_transfers.status.cancelled'), icon: 'x', color: 'gray' } default: return { - label: `Not handled: (${stockTransfer.status})`, + label: `${t('common.resources.common.status.not_handled')}: (${stockTransfer.status})`, icon: 'warning', color: 'white' } @@ -71,14 +74,16 @@ export function getStockTransferDisplayStatus( export function getStockTransferStatusName( status: StockTransfer['status'] ): string { + const { t } = useTranslation() + const dictionary: Record<typeof status, string> = { - draft: 'Draft', - upcoming: 'Upcoming', - on_hold: 'On hold', - picking: 'Picking', - in_transit: 'In transit', - completed: 'Completed', - cancelled: 'Cancelled' + cancelled: t('common.resources.stock_transfers.status.cancelled'), + completed: t('common.resources.stock_transfers.status.completed'), + draft: t('common.resources.stock_transfers.status.draft'), + in_transit: t('common.resources.stock_transfers.status.in_transit'), + on_hold: t('common.resources.stock_transfers.status.on_hold'), + picking: t('common.resources.stock_transfers.status.picking'), + upcoming: t('common.resources.stock_transfers.status.upcoming') } return dictionary[status] diff --git a/packages/i18n-locales/public/en.json b/packages/i18n-locales/public/en.json index 7b83c898..3a6b83ed 100644 --- a/packages/i18n-locales/public/en.json +++ b/packages/i18n-locales/public/en.json @@ -1,6 +1,121 @@ { "common": { "all": "all", - "all_female": "all" + "all_female": "all", + "resources": { + "common": { + "status": { + "not_handled": "Not handled" + } + }, + "bundles": { + "name": "Bundle", + "name_plural": "Bundles", + "status": {} + }, + "customers": { + "name": "Customer", + "name_plural": "Customers", + "status": { + "prospect": "Prospect", + "acquired": "Acquired", + "repeat": "Repeat" + } + }, + "orders": { + "name": "Order", + "name_plural": "Orders", + "status": { + "approved": "Approved", + "cancelled": "Cancelled", + "draft": "Draft", + "editing": "Editing", + "pending": "Pending", + "placed": "Placed", + "placing": "Placing", + "in_progress": "In progress", + "in_progress_manual": "In progress (Manual)" + }, + "payment_status": { + "authorized": "Authorized", + "paid": "Paid", + "unpaid": "Unpaid", + "free": "Free", + "voided": "Voided", + "refunded": "Refunded", + "partially_authorized": "Part. authorized", + "partially_paid": "Part. paid", + "partially_refunded": "Part. refunded", + "partially_voided": "Part. voided" + }, + "fulfillment_status": { + "unfulfilled": "Unfulfilled", + "in_progress": "In progress", + "fulfilled": "Fulfilled", + "not_required": "Not required" + }, + "task": { + "awaiting_approval": "Awaiting approval", + "error_to_cancel": "Error to cancel", + "payment_to_capture": "Payment to capture", + "fulfillment_in_progress": "Fulfillment in progress" + } + }, + "promotions": { + "name": "Promotion", + "name_plural": "Promotions", + "status": { + "active": "Active", + "disabled": "Disabled", + "expired": "Expired", + "inactive": "Inactive", + "pending": "Pending", + "upcoming": "Upcoming" + } + }, + "returns": { + "name": "Return", + "name_plural": "Returns", + "status": { + "approved": "Approved", + "cancelled": "Cancelled", + "draft": "Draft", + "requested": "Requested", + "received": "Received", + "rejected": "Rejected", + "refunded": "Refunded", + "shipped": "Shipped" + } + }, + "shipments": { + "name": "Shipment", + "name_plural": "Shipments", + "status": { + "awaiting_stock_transfer": "Awaiting stock transfer", + "cancelled": "Cancelled", + "delivered": "Delivered", + "draft": "Draft", + "on_hold": "On hold", + "packing": "Packing", + "picking": "Picking", + "ready_to_ship": "Ready to ship", + "shipped": "Shipped", + "upcoming": "Upcoming" + } + }, + "stock_transfers": { + "name": "Stock transfer", + "name_plural": "Stock transfers", + "status": { + "cancelled": "Cancelled", + "completed": "Completed", + "draft": "Draft", + "in_transit": "In transit", + "on_hold": "On hold", + "picking": "Picking", + "upcoming": "Upcoming" + } + } + } } } \ No newline at end of file diff --git a/packages/i18n-locales/public/it.json b/packages/i18n-locales/public/it.json index c36d1b69..2441bc6d 100644 --- a/packages/i18n-locales/public/it.json +++ b/packages/i18n-locales/public/it.json @@ -1,6 +1,121 @@ { "common": { "all": "tutti", - "all_female": "tutte" + "all_female": "tutte", + "resources": { + "common": { + "status": { + "not_handled": "Non gestito" + } + }, + "bundles": { + "name": "Bundle", + "name_plural": "Bundles", + "status": {} + }, + "customers": { + "name": "Customer", + "name_plural": "Customers", + "status": { + "prospect": "Potenziale", + "acquired": "Acquisito", + "repeat": "Abituale" + } + }, + "orders": { + "name": "Ordine", + "name_plural": "Ordini", + "status": { + "approved": "Approvato", + "cancelled": "Cancellato", + "draft": "Bozza", + "editing": "In modifica", + "pending": "In attesa", + "placed": "Piazzato", + "placing": "In piazzamento", + "in_progress": "In corso", + "in_progress_manual": "In corso (Manuale)" + }, + "payment_status": { + "authorized": "Autorizzato", + "paid": "Pagato", + "unpaid": "Non pagato", + "free": "Gratuito", + "voided": "Annullato", + "refunded": "Rimborsato", + "partially_authorized": "Parz. autorizzato", + "partially_paid": "Parz. pagato", + "partially_refunded": "Parz. rimborsato", + "partially_voided": "Parz. annullato" + }, + "fulfillment_status": { + "unfulfilled": "Non evaso", + "in_progress": "In corso", + "fulfilled": "Evaso", + "not_required": "Non richiesto" + }, + "task": { + "awaiting_approval": "In attesa di approvazione", + "error_to_cancel": "Errore nella cancellazione", + "payment_to_capture": "Pagamento da catturare", + "fulfillment_in_progress": "Evasione in corso" + } + }, + "promotions": { + "name": "Promozione", + "name_plural": "Promozioni", + "status": { + "active": "Attiva", + "disabled": "Disabilitata", + "expired": "Scaduta", + "inactive": "Inattiva", + "pending": "In attesa", + "upcoming": "Imminente" + } + }, + "returns": { + "name": "Reso", + "name_plural": "Resi", + "status": { + "approved": "Approvato", + "cancelled": "Cancellato", + "draft": "Bozza", + "requested": "Richiesto", + "received": "Ricevuto", + "refunded": "Rimborsato", + "rejected": "Rifiutato", + "shipped": "Spedito" + } + }, + "shipments": { + "name": "Spedizione", + "name_plural": "Spedizioni", + "status": { + "awaiting_stock_transfer": "In attesa di trasferimento di magazzino", + "cancelled": "Cancellato", + "delivered": "Consegnato", + "draft": "Bozza", + "on_hold": "In attesa", + "packing": "In imballaggio", + "picking": "In prelievo", + "ready_to_ship": "Pronto per la spedizione", + "shipped": "Spedito", + "upcoming": "Imminente" + } + }, + "stock_transfers": { + "name": "Trasferimento di magazzino", + "name_plural": "Trasferimenti di magazzino", + "status": { + "cancelled": "Cancellato", + "completed": "Completato", + "draft": "Bozza", + "in_transit": "In transito", + "on_hold": "In attesa", + "picking": "In prelievo", + "upcoming": "Imminente" + } + } + } } } \ No newline at end of file From bb31bddc57e99094f94c470bcc21131a2e2a50c7 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:00:27 +0100 Subject: [PATCH 07/33] fix: remove i18n package to ship locales with app-elements using `i18next-resources-to-backend` --- packages/app-elements/i18n.d.ts | 11 ++++++++++ packages/app-elements/package.json | 2 +- .../src/locales}/en.json | 0 .../src/locales}/it.json | 0 packages/app-elements/src/main.ts | 9 ++++++-- .../{ => I18NProvider}/I18NProvider.tsx | 12 ++++++----- .../I18NProvider}/i18n.ts | 21 ++++++++----------- .../src/providers/I18NProvider/index.tsx | 2 ++ packages/app-elements/tsconfig.json | 3 ++- .../src/stories/examples/I18n.stories.tsx | 2 +- packages/i18n-locales/package.json | 5 ----- pnpm-lock.yaml | 16 +++++++------- 12 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 packages/app-elements/i18n.d.ts rename packages/{i18n-locales/public => app-elements/src/locales}/en.json (100%) rename packages/{i18n-locales/public => app-elements/src/locales}/it.json (100%) rename packages/app-elements/src/providers/{ => I18NProvider}/I18NProvider.tsx (84%) rename packages/app-elements/src/{helpers => providers/I18NProvider}/i18n.ts (61%) create mode 100644 packages/app-elements/src/providers/I18NProvider/index.tsx delete mode 100644 packages/i18n-locales/package.json diff --git a/packages/app-elements/i18n.d.ts b/packages/app-elements/i18n.d.ts new file mode 100644 index 00000000..926ea0a8 --- /dev/null +++ b/packages/app-elements/i18n.d.ts @@ -0,0 +1,11 @@ +import 'i18next' +import type translation from './src/locales/en.json' + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: 'translation' + resources: { + translation: typeof translation + } + } +} diff --git a/packages/app-elements/package.json b/packages/app-elements/package.json index 73ec8a71..17d93cf6 100644 --- a/packages/app-elements/package.json +++ b/packages/app-elements/package.json @@ -47,7 +47,7 @@ "classnames": "^2.5.1", "i18next": "^24.0.5", "i18next-browser-languagedetector": "^8.0.0", - "i18next-http-backend": "^3.0.1", + "i18next-resources-to-backend": "^1.2.1", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", diff --git a/packages/i18n-locales/public/en.json b/packages/app-elements/src/locales/en.json similarity index 100% rename from packages/i18n-locales/public/en.json rename to packages/app-elements/src/locales/en.json diff --git a/packages/i18n-locales/public/it.json b/packages/app-elements/src/locales/it.json similarity index 100% rename from packages/i18n-locales/public/it.json rename to packages/app-elements/src/locales/it.json diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index b4bd912a..a890fcab 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -21,7 +21,6 @@ export { timeSeparator } from '#helpers/date' export { downloadJsonAsFile } from '#helpers/downloadJsonAsFile' -export { initI18n, languages, type I18NLocale } from '#helpers/i18n' export { computeFullname, formatDisplayName } from '#helpers/name' export { formatResourceName, @@ -62,7 +61,13 @@ export { export { createApp, type ClAppKey, type ClAppProps } from '#providers/createApp' export { ErrorBoundary } from '#providers/ErrorBoundary' export { GTMProvider, useTagManager } from '#providers/GTMProvider' -export { I18NProvider, useTranslation } from '#providers/I18NProvider' +export { + I18NProvider, + initI18n, + languages, + useTranslation, + type I18NLocale +} from '#providers/I18NProvider' export { MetaTags, TokenProvider, diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx similarity index 84% rename from packages/app-elements/src/providers/I18NProvider.tsx rename to packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx index c52bf0c9..8c06f934 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx @@ -1,4 +1,8 @@ -import { type I18NLocale, initI18n, languages } from '#helpers/i18n' +import { + type I18NLocale, + initI18n, + languages +} from '#providers/I18NProvider/i18n' import { type i18n as I18nInstance } from 'i18next' import React, { type ReactNode, useEffect, useState } from 'react' import { I18nextProvider } from 'react-i18next' @@ -7,13 +11,11 @@ export { useTranslation } from 'react-i18next' interface I18NProviderProps { localeCode?: I18NLocale - baseUrl?: string children: ReactNode } export const I18NProvider: React.FC<I18NProviderProps> = ({ localeCode = languages[0], - baseUrl, children }) => { const [i18nInstance, setI18nInstance] = useState<I18nInstance | undefined>() @@ -21,7 +23,7 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ useEffect(() => { const setupI18n = async (): Promise<void> => { try { - const instance = await initI18n(localeCode, baseUrl) + const instance = await initI18n(localeCode) if (instance.isInitialized) { setI18nInstance(instance) } @@ -36,7 +38,7 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ ) { void setupI18n() } - }, [localeCode, baseUrl, i18nInstance]) + }, [localeCode, i18nInstance]) if ( i18nInstance == null || diff --git a/packages/app-elements/src/helpers/i18n.ts b/packages/app-elements/src/providers/I18NProvider/i18n.ts similarity index 61% rename from packages/app-elements/src/helpers/i18n.ts rename to packages/app-elements/src/providers/I18NProvider/i18n.ts index 9715829f..722f5483 100644 --- a/packages/app-elements/src/helpers/i18n.ts +++ b/packages/app-elements/src/providers/I18NProvider/i18n.ts @@ -1,6 +1,6 @@ import i18n, { type i18n as I18nInstance } from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' -import HttpApi, { type HttpBackendOptions } from 'i18next-http-backend' +import resourcesToBackend from 'i18next-resources-to-backend' import { initReactI18next } from 'react-i18next' export const languages = ['en', 'it'] as const @@ -8,28 +8,25 @@ export const languages = ['en', 'it'] as const export type I18NLocale = (typeof languages)[number] export const initI18n = async ( - localeCode: I18NLocale, - baseUrl?: string + localeCode: I18NLocale ): Promise<I18nInstance> => { - // TODO: Define the path to the i18n public files - const localeUrl = `${baseUrl ?? 'https://cdn.commercelayer.io/i18n/'}{{lng}}.json` - await i18n - .use(HttpApi) + .use( + resourcesToBackend( + async (language: I18NLocale) => + await import(`../../locales/${language}.json`) + ) + ) .use(LanguageDetector) .use(initReactI18next) - .init<HttpBackendOptions>({ + .init({ load: 'languageOnly', supportedLngs: languages, lng: localeCode, fallbackLng: languages[0], - preload: ['en'], react: { useSuspense: true }, - backend: { - loadPath: localeUrl - }, debug: true }) return i18n diff --git a/packages/app-elements/src/providers/I18NProvider/index.tsx b/packages/app-elements/src/providers/I18NProvider/index.tsx new file mode 100644 index 00000000..e3daad63 --- /dev/null +++ b/packages/app-elements/src/providers/I18NProvider/index.tsx @@ -0,0 +1,2 @@ +export { initI18n, languages, type I18NLocale } from './i18n' +export { I18NProvider, useTranslation } from './I18NProvider' diff --git a/packages/app-elements/tsconfig.json b/packages/app-elements/tsconfig.json index 3630ac43..74e71d38 100644 --- a/packages/app-elements/tsconfig.json +++ b/packages/app-elements/tsconfig.json @@ -53,7 +53,8 @@ "vite.config.js", "src", "mocks", - ".eslintrc.cjs" + ".eslintrc.cjs", + "i18n.d.ts" ], "references": [ { diff --git a/packages/docs/src/stories/examples/I18n.stories.tsx b/packages/docs/src/stories/examples/I18n.stories.tsx index 38268ff4..aa4b5cb1 100644 --- a/packages/docs/src/stories/examples/I18n.stories.tsx +++ b/packages/docs/src/stories/examples/I18n.stories.tsx @@ -19,7 +19,7 @@ const setup: Meta = { docs: { page: () => ( <> - <I18NProvider localeCode='en' baseUrl='http://localhost:3000/'> + <I18NProvider localeCode='en'> <Title /> <Subtitle /> <Description /> diff --git a/packages/i18n-locales/package.json b/packages/i18n-locales/package.json deleted file mode 100644 index 8f914022..00000000 --- a/packages/i18n-locales/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "serve": "pnpm dlx serve --cors public" - } -} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55cd2f28..021f45bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,9 @@ importers: i18next-browser-languagedetector: specifier: ^8.0.0 version: 8.0.0 - i18next-http-backend: - specifier: ^3.0.1 - version: 3.0.1(encoding@0.1.13) + i18next-resources-to-backend: + specifier: ^1.2.1 + version: 1.2.1 js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -3764,8 +3764,8 @@ packages: i18next-browser-languagedetector@8.0.0: resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} - i18next-http-backend@3.0.1: - resolution: {integrity: sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==} + i18next-resources-to-backend@1.2.1: + resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} i18next@24.0.5: resolution: {integrity: sha512-1jSdEzgFPGLZRsQwydoMFCBBaV+PmrVEO5WhANllZPX4y2JSGTxUjJ+xVklHIsiS95uR8gYc/y0hYZWevucNjg==} @@ -10649,11 +10649,9 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 - i18next-http-backend@3.0.1(encoding@0.1.13): + i18next-resources-to-backend@1.2.1: dependencies: - cross-fetch: 4.0.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding + '@babel/runtime': 7.26.0 i18next@24.0.5(typescript@5.7.2): dependencies: From 310c083fef820a3322a0e73ed426b38254004d8f Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:02:45 +0100 Subject: [PATCH 08/33] fix: remove `useTranslation` in functions (non-react components) --- packages/app-elements/src/dictionaries/customers.ts | 6 +----- packages/app-elements/src/dictionaries/orders.ts | 2 -- packages/app-elements/src/dictionaries/promotions.ts | 3 +-- packages/app-elements/src/dictionaries/shipments.ts | 6 +----- packages/app-elements/src/dictionaries/stockTransfers.ts | 6 +----- 5 files changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/app-elements/src/dictionaries/customers.ts b/packages/app-elements/src/dictionaries/customers.ts index 5882a2b6..8f58fff5 100644 --- a/packages/app-elements/src/dictionaries/customers.ts +++ b/packages/app-elements/src/dictionaries/customers.ts @@ -1,5 +1,5 @@ -import { useTranslation } from '#providers/I18NProvider' import type { Customer } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' export interface CustomerDisplayStatus extends DisplayStatus {} @@ -7,8 +7,6 @@ export interface CustomerDisplayStatus extends DisplayStatus {} export function getCustomerDisplayStatus( customerObj: Customer ): CustomerDisplayStatus { - const { t } = useTranslation() - switch (customerObj.status) { case 'prospect': return { @@ -37,8 +35,6 @@ export function getCustomerDisplayStatus( } export function getCustomerStatusName(status: Customer['status']): string { - const { t } = useTranslation() - const dictionary: Record<typeof status, string> = { prospect: t('common.resources.customers.status.prospect'), acquired: t('common.resources.customers.status.acquired'), diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index 7f8bc31e..da0d6349 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -1,4 +1,3 @@ -import { useTranslation } from '#providers/I18NProvider' import type { StatusIconProps } from '#ui/atoms/StatusIcon' import type { Order } from '@commercelayer/sdk' import { t } from 'i18next' @@ -11,7 +10,6 @@ export interface OrderDisplayStatus extends DisplayStatus { } export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { - const { t } = useTranslation() const combinedStatus = `${order.status}:${order.payment_status}:${order.fulfillment_status}` as const diff --git a/packages/app-elements/src/dictionaries/promotions.ts b/packages/app-elements/src/dictionaries/promotions.ts index 13409504..85d5824a 100644 --- a/packages/app-elements/src/dictionaries/promotions.ts +++ b/packages/app-elements/src/dictionaries/promotions.ts @@ -1,6 +1,6 @@ import { getEventDateInfo } from '#helpers/date' -import { useTranslation } from '#providers/I18NProvider' import type { Promotion } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' interface PromotionDisplayStatus extends DisplayStatus { @@ -10,7 +10,6 @@ interface PromotionDisplayStatus extends DisplayStatus { export function getPromotionDisplayStatus( promotion: Omit<Promotion, 'type' | 'promotion_rules'> ): PromotionDisplayStatus { - const { t } = useTranslation() if (promotion.disabled_at != null) { return { status: 'disabled', diff --git a/packages/app-elements/src/dictionaries/shipments.ts b/packages/app-elements/src/dictionaries/shipments.ts index 4093eb94..52fee2cd 100644 --- a/packages/app-elements/src/dictionaries/shipments.ts +++ b/packages/app-elements/src/dictionaries/shipments.ts @@ -1,6 +1,6 @@ -import { useTranslation } from '#providers/I18NProvider' import { type StatusIconProps } from '#ui/atoms/StatusIcon' import type { Shipment } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' export interface ShipmentDisplayStatus extends DisplayStatus { @@ -14,8 +14,6 @@ export function getShipmentDisplayStatus( shipment: Shipment, awaitingStockTransfer: boolean = false ): ShipmentDisplayStatus { - const { t } = useTranslation() - const shipmentStatus = awaitingStockTransfer ? 'awaiting_stock_transfer' : shipment.status @@ -106,8 +104,6 @@ export function getShipmentDisplayStatus( } export function getShipmentStatusName(status: Shipment['status']): string { - const { t } = useTranslation() - const dictionary: Record<typeof status, string> = { draft: t('common.resources.shipments.status.draft'), on_hold: t('common.resources.shipments.status.on_hold'), diff --git a/packages/app-elements/src/dictionaries/stockTransfers.ts b/packages/app-elements/src/dictionaries/stockTransfers.ts index eb0cf11d..efbd22e5 100644 --- a/packages/app-elements/src/dictionaries/stockTransfers.ts +++ b/packages/app-elements/src/dictionaries/stockTransfers.ts @@ -1,6 +1,6 @@ -import { useTranslation } from '#providers/I18NProvider' import { type StatusIconProps } from '#ui/atoms/StatusIcon' import type { StockTransfer } from '@commercelayer/sdk' +import { t } from 'i18next' import type { DisplayStatus } from './types' export interface StockTransferDisplayStatus extends DisplayStatus { @@ -13,8 +13,6 @@ export interface StockTransferDisplayStatus extends DisplayStatus { export function getStockTransferDisplayStatus( stockTransfer: StockTransfer ): StockTransferDisplayStatus { - const { t } = useTranslation() - switch (stockTransfer.status) { case 'upcoming': return { @@ -74,8 +72,6 @@ export function getStockTransferDisplayStatus( export function getStockTransferStatusName( status: StockTransfer['status'] ): string { - const { t } = useTranslation() - const dictionary: Record<typeof status, string> = { cancelled: t('common.resources.stock_transfers.status.cancelled'), completed: t('common.resources.stock_transfers.status.completed'), From b2a36d0de1252d6330981170c7ec349b02c413b0 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Mon, 16 Dec 2024 09:28:52 +0100 Subject: [PATCH 09/33] feat: add translations for hooks --- .../src/hooks/useEditMetadataOverlay.tsx | 6 ++++- .../src/hooks/useEditTagsOverlay.tsx | 24 +++++++++++++------ packages/app-elements/src/locales/en.json | 19 +++++++++++++-- packages/app-elements/src/locales/it.json | 19 +++++++++++++-- pnpm-lock.yaml | 2 -- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx b/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx index e7e17c78..0dea92ee 100644 --- a/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx +++ b/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx @@ -1,4 +1,5 @@ import { useOverlay } from '#hooks/useOverlay' +import { useTranslation } from '#providers/I18NProvider' import { PageLayout } from '#ui/composite/PageLayout' import { type ResourceMetadataProps } from '#ui/resources/ResourceMetadata' import { ResourceMetadataForm } from '#ui/resources/ResourceMetadata/ResourceMetadataForm' @@ -20,13 +21,16 @@ interface MetadataOverlayHook { export function useEditMetadataOverlay(): MetadataOverlayHook { const { Overlay: OverlayElement, open, close } = useOverlay() + const { t } = useTranslation() const OverlayComponent = useCallback<FC<EditMetadataOverlayProps>>( ({ title = 'Back', resourceId, resourceType }) => { return ( <OverlayElement backgroundColor='light'> <PageLayout - title='Edit metadata' + title={t('common.edit', { + resource: t('common.metadata').toLowerCase() + })} minHeight={false} navigationButton={{ label: title, diff --git a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx index 3924144f..f6bcb69f 100644 --- a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx +++ b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx @@ -1,6 +1,7 @@ import { navigateTo } from '#helpers/appsNavigation' import { useOverlay } from '#hooks/useOverlay' import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { useTranslation } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { Text } from '#ui/atoms/Text' @@ -41,6 +42,7 @@ export function useEditTagsOverlay(): TagsOverlayHook { } = useOverlay({ queryParam: 'edit-tags' }) const { settings } = useTokenProvider() + const { t } = useTranslation() const [selectedTagsLimitReached, setSelectedTagsLimitReached] = useState(false) @@ -52,6 +54,8 @@ export function useEditTagsOverlay(): TagsOverlayHook { } }) + const resourceName = t('common.resources.tags.name_other') + return { show: open, Overlay: ({ @@ -133,12 +137,12 @@ export function useEditTagsOverlay(): TagsOverlayHook { }) }} > - Update + {t('common.update')} </Button> } > <PageLayout - title='Edit tags' + title={t('common.edit', { resource: resourceName.toLowerCase() })} minHeight={false} navigationButton={{ label: title, @@ -152,7 +156,9 @@ export function useEditTagsOverlay(): TagsOverlayHook { showManageAction != null && showManageAction ? [ { - label: 'Manage tags', + label: t('common.manage', { + resource: resourceName.toLowerCase() + }), variant: 'secondary', size: 'small', onClick: navigateToTagsManagement?.onClick @@ -162,17 +168,21 @@ export function useEditTagsOverlay(): TagsOverlayHook { }} > <InputSelect - label='Tags' - placeholder='Search...' + label={resourceName} + placeholder={t('common.search')} hint={{ text: ( <> - You can add up to 10 tags. + {t('common.add_up_to', { + limit: 10, + resource: resourceName + })} + . {selectedTagsLimitReached && ( <> {' '} <Text weight='bold' variant='warning'> - Limit reached + {t('common.limit_reached')} </Text> . </> diff --git a/packages/app-elements/src/locales/en.json b/packages/app-elements/src/locales/en.json index 3a6b83ed..e55f7ec0 100644 --- a/packages/app-elements/src/locales/en.json +++ b/packages/app-elements/src/locales/en.json @@ -1,7 +1,17 @@ { "common": { - "all": "all", - "all_female": "all", + "all": "All {{resource}}", + "all_female": "All {{resource}}", + "not_handled": "Not handled", + "edit": "Edit {{resource}}", + "manage": "Manage {{resource}}", + "new": "New {{resource}}", + "new_female": "New {{resource}}", + "metadata": "Metadata", + "search": "Search...", + "limit_reached": "Limit reached", + "add_up_to": "You can add up to {{limit}} {{resource}}.", + "update": "Update", "resources": { "common": { "status": { @@ -115,6 +125,11 @@ "picking": "Picking", "upcoming": "Upcoming" } + }, + "tags": { + "name": "Tag", + "name_other": "Tags", + "status": {} } } } diff --git a/packages/app-elements/src/locales/it.json b/packages/app-elements/src/locales/it.json index 2441bc6d..d6394fd4 100644 --- a/packages/app-elements/src/locales/it.json +++ b/packages/app-elements/src/locales/it.json @@ -1,7 +1,17 @@ { "common": { - "all": "tutti", - "all_female": "tutte", + "all": "Tutti {{resource}}", + "all_female": "Tutte {{resource}}", + "not_handled": "Non gestito", + "edit": "Modifica {{resource}}", + "manage": "Gestisci {{resource}}", + "new_resource": "Nuovo {{resource}}", + "new_resource_female": "Nuova {{resource}}", + "metadata": "Metadati", + "search": "Cerca...", + "limit_reached": "Limite raggiunto", + "add_up_to": "Puoi aggiungere fino a {{limit}} {{resource}}.", + "update": "Aggiorna", "resources": { "common": { "status": { @@ -115,6 +125,11 @@ "picking": "In prelievo", "upcoming": "Imminente" } + }, + "tags": { + "name": "Tag", + "name_other": "Tag", + "status": {} } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 021f45bb..3dd8bd32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -323,8 +323,6 @@ importers: specifier: ^3.23.8 version: 3.23.8 - packages/i18n-locales: {} - packages: '@adobe/css-tools@4.4.1': From a59be3fa2a9b6e4abb57e8404a6de447a21c8cc2 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:53:33 +0100 Subject: [PATCH 10/33] fix: use `ts` file to improve type definitions in consumer apps --- packages/app-elements/i18n.d.ts | 2 +- packages/app-elements/package.json | 3 + packages/app-elements/src/locales/en.json | 136 ------------ packages/app-elements/src/locales/en.ts | 195 +++++++++++++++++ packages/app-elements/src/locales/it.json | 136 ------------ packages/app-elements/src/locales/it.ts | 197 ++++++++++++++++++ packages/app-elements/src/main.ts | 18 +- .../providers/I18NProvider/I18NProvider.tsx | 1 + .../src/providers/I18NProvider/i18n.ts | 2 +- .../src/providers/I18NProvider/index.tsx | 2 +- packages/app-elements/vite.config.js | 33 ++- 11 files changed, 440 insertions(+), 285 deletions(-) delete mode 100644 packages/app-elements/src/locales/en.json create mode 100644 packages/app-elements/src/locales/en.ts delete mode 100644 packages/app-elements/src/locales/it.json create mode 100644 packages/app-elements/src/locales/it.ts diff --git a/packages/app-elements/i18n.d.ts b/packages/app-elements/i18n.d.ts index 926ea0a8..2f23a94a 100644 --- a/packages/app-elements/i18n.d.ts +++ b/packages/app-elements/i18n.d.ts @@ -1,5 +1,5 @@ import 'i18next' -import type translation from './src/locales/en.json' +import type translation from './src/locales/en' declare module 'i18next' { interface CustomTypeOptions { diff --git a/packages/app-elements/package.json b/packages/app-elements/package.json index 17d93cf6..a0e3902d 100644 --- a/packages/app-elements/package.json +++ b/packages/app-elements/package.json @@ -20,6 +20,9 @@ }, "./tailwind.config": { "require": "./dist/tailwind.config.js" + }, + "./i18n.d.ts": { + "require": "./dist/i18n.d.ts" } }, "engines": { diff --git a/packages/app-elements/src/locales/en.json b/packages/app-elements/src/locales/en.json deleted file mode 100644 index e55f7ec0..00000000 --- a/packages/app-elements/src/locales/en.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "common": { - "all": "All {{resource}}", - "all_female": "All {{resource}}", - "not_handled": "Not handled", - "edit": "Edit {{resource}}", - "manage": "Manage {{resource}}", - "new": "New {{resource}}", - "new_female": "New {{resource}}", - "metadata": "Metadata", - "search": "Search...", - "limit_reached": "Limit reached", - "add_up_to": "You can add up to {{limit}} {{resource}}.", - "update": "Update", - "resources": { - "common": { - "status": { - "not_handled": "Not handled" - } - }, - "bundles": { - "name": "Bundle", - "name_plural": "Bundles", - "status": {} - }, - "customers": { - "name": "Customer", - "name_plural": "Customers", - "status": { - "prospect": "Prospect", - "acquired": "Acquired", - "repeat": "Repeat" - } - }, - "orders": { - "name": "Order", - "name_plural": "Orders", - "status": { - "approved": "Approved", - "cancelled": "Cancelled", - "draft": "Draft", - "editing": "Editing", - "pending": "Pending", - "placed": "Placed", - "placing": "Placing", - "in_progress": "In progress", - "in_progress_manual": "In progress (Manual)" - }, - "payment_status": { - "authorized": "Authorized", - "paid": "Paid", - "unpaid": "Unpaid", - "free": "Free", - "voided": "Voided", - "refunded": "Refunded", - "partially_authorized": "Part. authorized", - "partially_paid": "Part. paid", - "partially_refunded": "Part. refunded", - "partially_voided": "Part. voided" - }, - "fulfillment_status": { - "unfulfilled": "Unfulfilled", - "in_progress": "In progress", - "fulfilled": "Fulfilled", - "not_required": "Not required" - }, - "task": { - "awaiting_approval": "Awaiting approval", - "error_to_cancel": "Error to cancel", - "payment_to_capture": "Payment to capture", - "fulfillment_in_progress": "Fulfillment in progress" - } - }, - "promotions": { - "name": "Promotion", - "name_plural": "Promotions", - "status": { - "active": "Active", - "disabled": "Disabled", - "expired": "Expired", - "inactive": "Inactive", - "pending": "Pending", - "upcoming": "Upcoming" - } - }, - "returns": { - "name": "Return", - "name_plural": "Returns", - "status": { - "approved": "Approved", - "cancelled": "Cancelled", - "draft": "Draft", - "requested": "Requested", - "received": "Received", - "rejected": "Rejected", - "refunded": "Refunded", - "shipped": "Shipped" - } - }, - "shipments": { - "name": "Shipment", - "name_plural": "Shipments", - "status": { - "awaiting_stock_transfer": "Awaiting stock transfer", - "cancelled": "Cancelled", - "delivered": "Delivered", - "draft": "Draft", - "on_hold": "On hold", - "packing": "Packing", - "picking": "Picking", - "ready_to_ship": "Ready to ship", - "shipped": "Shipped", - "upcoming": "Upcoming" - } - }, - "stock_transfers": { - "name": "Stock transfer", - "name_plural": "Stock transfers", - "status": { - "cancelled": "Cancelled", - "completed": "Completed", - "draft": "Draft", - "in_transit": "In transit", - "on_hold": "On hold", - "picking": "Picking", - "upcoming": "Upcoming" - } - }, - "tags": { - "name": "Tag", - "name_other": "Tags", - "status": {} - } - } - } -} \ No newline at end of file diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts new file mode 100644 index 00000000..34be0b98 --- /dev/null +++ b/packages/app-elements/src/locales/en.ts @@ -0,0 +1,195 @@ +export const en = { + common: { + all: 'All {{resource}}', + all_female: 'All {{resource}}', + not_handled: 'Not handled', + back: 'Back', + new: 'New', + new_resource: 'New {{resource}}', + new_resource_female: 'New {{resource}}', + not_authorized: 'Not authorized', + no_items: 'No items', + edit: 'Edit', + manage: 'Manage {{resource}}', + updated: 'Updated', + timeline: 'Timeline', + filters: 'Filters', + metadata: 'Metadata', + search: 'Search...', + limit_reached: 'Limit reached', + add_up_to: 'You can add up to {{limit}} {{resource}}.', + update: 'Update', + resources: { + common: { + status: { + not_handled: 'Not handled' + } + }, + adjustments: { + name: 'Adjustment', + name_other: 'Adjustments', + task: { + adjust_total: 'Adjust total' + } + }, + bundles: { + name: 'Bundle', + name_other: 'Bundles', + status: {} + }, + customers: { + name: 'Customer', + name_other: 'Customers', + status: { + prospect: 'Prospect', + acquired: 'Acquired', + repeat: 'Repeat' + } + }, + orders: { + name: 'Order', + name_other: 'Orders', + status: { + name: 'Status', + approved: 'Approved', + cancelled: 'Cancelled', + draft: 'Draft', + editing: 'Editing', + pending: 'Pending', + placed: 'Placed', + placing: 'Placing', + in_progress: 'In progress', + in_progress_manual: 'In progress (Manual)' + }, + payment_status: { + name: 'Payment status', + name_short: 'Payment', + authorized: 'Authorized', + paid: 'Paid', + unpaid: 'Unpaid', + free: 'Free', + voided: 'Voided', + refunded: 'Refunded', + partially_authorized: 'Part. authorized', + partially_paid: 'Part. paid', + partially_refunded: 'Part. refunded', + partially_voided: 'Part. voided' + }, + fulfillment_status: { + name: 'Fulfillment status', + name_short: 'Fulfillment', + unfulfilled: 'Unfulfilled', + in_progress: 'In progress', + fulfilled: 'Fulfilled', + not_required: 'Not required' + }, + task: { + open: 'Open', + browse: 'Browse', + awaiting_approval: 'Awaiting approval', + error_to_cancel: 'Error to cancel', + payment_to_capture: 'Payment to capture', + fulfillment_in_progress: 'Fulfillment in progress', + editing: 'Editing', + history: 'Order history', + carts: 'Carts', + archived: 'Archived' + }, + details: { + summary: 'Summary', + to_be_calculated: 'To be calculated', + shipping: 'Shipping', + subtotal: 'Subtotal', + total: 'Total', + payment_method: 'Payment method', + taxes: 'Taxes', + included: 'included', + discount: 'Discount', + billing_address: 'Billing address', + shipping_address: 'Shipping address' + }, + actions: { + add_item: 'Add item', + approve: 'Approve', + archive: 'Archive', + cancel_transactions: 'Cancel payment', + cancel: 'Cancel order', + capture: 'Capture payment', + place: 'Place order', + refund: 'Refund', + unarchive: 'Unarchive', + select_address: 'Select address' + } + }, + gift_cards: { + name: 'Gift card', + name_other: 'Gift cards' + }, + promotions: { + name: 'Promotion', + name_other: 'Promotions', + status: { + active: 'Active', + disabled: 'Disabled', + expired: 'Expired', + inactive: 'Inactive', + pending: 'Pending', + upcoming: 'Upcoming' + } + }, + returns: { + name: 'Return', + name_other: 'Returns', + status: { + approved: 'Approved', + cancelled: 'Cancelled', + draft: 'Draft', + requested: 'Requested', + received: 'Received', + rejected: 'Rejected', + refunded: 'Refunded', + shipped: 'Shipped' + } + }, + shipments: { + name: 'Shipment', + name_other: 'Shipments', + status: { + awaiting_stock_transfer: 'Awaiting stock transfer', + cancelled: 'Cancelled', + delivered: 'Delivered', + draft: 'Draft', + on_hold: 'On hold', + packing: 'Packing', + picking: 'Picking', + ready_to_ship: 'Ready to ship', + shipped: 'Shipped', + upcoming: 'Upcoming' + } + }, + stock_transfers: { + name: 'Stock transfer', + name_other: 'Stock transfers', + status: { + cancelled: 'Cancelled', + completed: 'Completed', + draft: 'Draft', + in_transit: 'In transit', + on_hold: 'On hold', + picking: 'Picking', + upcoming: 'Upcoming' + } + }, + tags: { + name: 'Tag', + name_other: 'Tags', + status: {} + } + }, + validation: { + select_one_item: 'Please select at least one item' + } + } +} + +export default en diff --git a/packages/app-elements/src/locales/it.json b/packages/app-elements/src/locales/it.json deleted file mode 100644 index d6394fd4..00000000 --- a/packages/app-elements/src/locales/it.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "common": { - "all": "Tutti {{resource}}", - "all_female": "Tutte {{resource}}", - "not_handled": "Non gestito", - "edit": "Modifica {{resource}}", - "manage": "Gestisci {{resource}}", - "new_resource": "Nuovo {{resource}}", - "new_resource_female": "Nuova {{resource}}", - "metadata": "Metadati", - "search": "Cerca...", - "limit_reached": "Limite raggiunto", - "add_up_to": "Puoi aggiungere fino a {{limit}} {{resource}}.", - "update": "Aggiorna", - "resources": { - "common": { - "status": { - "not_handled": "Non gestito" - } - }, - "bundles": { - "name": "Bundle", - "name_plural": "Bundles", - "status": {} - }, - "customers": { - "name": "Customer", - "name_plural": "Customers", - "status": { - "prospect": "Potenziale", - "acquired": "Acquisito", - "repeat": "Abituale" - } - }, - "orders": { - "name": "Ordine", - "name_plural": "Ordini", - "status": { - "approved": "Approvato", - "cancelled": "Cancellato", - "draft": "Bozza", - "editing": "In modifica", - "pending": "In attesa", - "placed": "Piazzato", - "placing": "In piazzamento", - "in_progress": "In corso", - "in_progress_manual": "In corso (Manuale)" - }, - "payment_status": { - "authorized": "Autorizzato", - "paid": "Pagato", - "unpaid": "Non pagato", - "free": "Gratuito", - "voided": "Annullato", - "refunded": "Rimborsato", - "partially_authorized": "Parz. autorizzato", - "partially_paid": "Parz. pagato", - "partially_refunded": "Parz. rimborsato", - "partially_voided": "Parz. annullato" - }, - "fulfillment_status": { - "unfulfilled": "Non evaso", - "in_progress": "In corso", - "fulfilled": "Evaso", - "not_required": "Non richiesto" - }, - "task": { - "awaiting_approval": "In attesa di approvazione", - "error_to_cancel": "Errore nella cancellazione", - "payment_to_capture": "Pagamento da catturare", - "fulfillment_in_progress": "Evasione in corso" - } - }, - "promotions": { - "name": "Promozione", - "name_plural": "Promozioni", - "status": { - "active": "Attiva", - "disabled": "Disabilitata", - "expired": "Scaduta", - "inactive": "Inattiva", - "pending": "In attesa", - "upcoming": "Imminente" - } - }, - "returns": { - "name": "Reso", - "name_plural": "Resi", - "status": { - "approved": "Approvato", - "cancelled": "Cancellato", - "draft": "Bozza", - "requested": "Richiesto", - "received": "Ricevuto", - "refunded": "Rimborsato", - "rejected": "Rifiutato", - "shipped": "Spedito" - } - }, - "shipments": { - "name": "Spedizione", - "name_plural": "Spedizioni", - "status": { - "awaiting_stock_transfer": "In attesa di trasferimento di magazzino", - "cancelled": "Cancellato", - "delivered": "Consegnato", - "draft": "Bozza", - "on_hold": "In attesa", - "packing": "In imballaggio", - "picking": "In prelievo", - "ready_to_ship": "Pronto per la spedizione", - "shipped": "Spedito", - "upcoming": "Imminente" - } - }, - "stock_transfers": { - "name": "Trasferimento di magazzino", - "name_plural": "Trasferimenti di magazzino", - "status": { - "cancelled": "Cancellato", - "completed": "Completato", - "draft": "Bozza", - "in_transit": "In transito", - "on_hold": "In attesa", - "picking": "In prelievo", - "upcoming": "Imminente" - } - }, - "tags": { - "name": "Tag", - "name_other": "Tag", - "status": {} - } - } - } -} \ No newline at end of file diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts new file mode 100644 index 00000000..79c283ad --- /dev/null +++ b/packages/app-elements/src/locales/it.ts @@ -0,0 +1,197 @@ +import type { en } from './en' + +const it: typeof en = { + common: { + all: 'Tutti {{resource}}', + all_female: 'Tutte {{resource}}', + not_handled: 'Non gestito', + back: 'Torna indietro', + new: 'Nuovo', + new_resource: 'Nuovo {{resource}}', + new_resource_female: 'Nuova {{resource}}', + not_authorized: 'Non autorizzato', + no_items: 'Nessun elemento', + edit: 'Modifica', + manage: 'Gestisci {{resource}}', + updated: 'Aggiornato', + timeline: 'Storico', + filters: 'Filtri', + metadata: 'Metadati', + search: 'Cerca...', + limit_reached: 'Limite raggiunto', + add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', + update: 'Aggiorna', + resources: { + common: { + status: { + not_handled: 'Non gestito' + } + }, + adjustments: { + name: 'Rettifica', + name_other: 'Rettifiche', + task: { + adjust_total: 'Rettifica totale' + } + }, + bundles: { + name: 'Bundle', + name_other: 'Bundles', + status: {} + }, + customers: { + name: 'Cliente', + name_other: 'Clienti', + status: { + prospect: 'Potenziale', + acquired: 'Acquisito', + repeat: 'Abituale' + } + }, + orders: { + name: 'Ordine', + name_other: 'Ordini', + status: { + name: 'Stato', + approved: 'Approvato', + cancelled: 'Cancellato', + draft: 'Bozza', + editing: 'In modifica', + pending: 'In attesa', + placed: 'Piazzato', + placing: 'In piazzamento', + in_progress: 'In corso', + in_progress_manual: 'In corso (Manuale)' + }, + payment_status: { + name: 'Stato pagamento', + name_short: 'Pagamento', + authorized: 'Autorizzato', + paid: 'Pagato', + unpaid: 'Non pagato', + free: 'Gratuito', + voided: 'Annullato', + refunded: 'Rimborsato', + partially_authorized: 'Parz. autorizzato', + partially_paid: 'Parz. pagato', + partially_refunded: 'Parz. rimborsato', + partially_voided: 'Parz. annullato' + }, + fulfillment_status: { + name: 'Stato evasione', + name_short: 'Evasione', + unfulfilled: 'Non evaso', + in_progress: 'In corso', + fulfilled: 'Evaso', + not_required: 'Non richiesto' + }, + task: { + open: 'Aperti', + browse: 'Sfoglia', + awaiting_approval: 'In attesa di approvazione', + error_to_cancel: 'Errore nella cancellazione', + payment_to_capture: 'Pagamento da catturare', + fulfillment_in_progress: 'Evasione in corso', + editing: 'In modifica', + history: 'Tutti gli ordini', + carts: 'Carrelli', + archived: 'Archiviati' + }, + details: { + summary: 'Riepilogo', + to_be_calculated: 'Da calcolare', + shipping: 'Spedizione', + subtotal: 'Totale parziale', + total: 'Totale', + payment_method: 'Metodo di pagamento', + taxes: 'Tasse', + included: 'incluse', + discount: 'Sconto', + billing_address: 'Indirizzo di fatturazione', + shipping_address: 'Indirizzo di spedizione' + }, + actions: { + add_item: 'Aggiungi prodotto', + approve: 'Approva', + archive: 'Archivia', + cancel_transactions: 'Annulla pagamento', + cancel: 'Annulla ordine', + capture: 'Cattura pagamento', + place: 'Piazza ordine', + refund: 'Rimborsa', + unarchive: 'Ripristina', + select_address: 'Seleziona indirizzo' + } + }, + gift_cards: { + name: 'Carta regalo', + name_other: 'Carte regalo' + }, + promotions: { + name: 'Promozione', + name_other: 'Promozioni', + status: { + active: 'Attiva', + disabled: 'Disabilitata', + expired: 'Scaduta', + inactive: 'Inattiva', + pending: 'In attesa', + upcoming: 'Imminente' + } + }, + returns: { + name: 'Reso', + name_other: 'Resi', + status: { + approved: 'Approvato', + cancelled: 'Cancellato', + draft: 'Bozza', + requested: 'Richiesto', + received: 'Ricevuto', + refunded: 'Rimborsato', + rejected: 'Rifiutato', + shipped: 'Spedito' + } + }, + shipments: { + name: 'Spedizione', + name_other: 'Spedizioni', + status: { + awaiting_stock_transfer: 'In attesa di trasferimento di magazzino', + cancelled: 'Cancellato', + delivered: 'Consegnato', + draft: 'Bozza', + on_hold: 'In attesa', + packing: 'In imballaggio', + picking: 'In prelievo', + ready_to_ship: 'Pronto per la spedizione', + shipped: 'Spedito', + upcoming: 'Imminente' + } + }, + stock_transfers: { + name: 'Trasferimento di magazzino', + name_other: 'Trasferimenti di magazzino', + status: { + cancelled: 'Cancellato', + completed: 'Completato', + draft: 'Bozza', + in_transit: 'In transito', + on_hold: 'In attesa', + picking: 'In prelievo', + upcoming: 'Imminente' + } + }, + tags: { + name: 'Tag', + name_other: 'Tag', + status: {} + } + }, + validation: { + select_one_item: 'Seleziona almeno un elemento' + } + } +} + +export default it diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index a890fcab..2958fbdf 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -65,13 +65,14 @@ export { I18NProvider, initI18n, languages, + t, useTranslation, type I18NLocale } from '#providers/I18NProvider' export { + encodeExtras, MetaTags, TokenProvider, - encodeExtras, useTokenProvider, type TokenProviderAllowedApp, type TokenProviderExtras, @@ -180,10 +181,10 @@ export { PageLayout, type PageLayoutProps } from '#ui/composite/PageLayout' export { PageSkeleton } from '#ui/composite/PageSkeleton' export { Report, type ReportProps } from '#ui/composite/Report' export { - GenericPageNotFound, - Routes, createRoute, createTypedRoute, + GenericPageNotFound, + Routes, type GetParams, type PageProps, type Route @@ -223,9 +224,9 @@ export { type InputCheckboxGroupProps } from '#ui/forms/InputCheckboxGroup' export { + formatCentsToCurrency, HookedInputCurrency, InputCurrency, - formatCentsToCurrency, type HookedInputCurrencyProps, type InputCurrencyProps } from '#ui/forms/InputCurrency' @@ -262,10 +263,10 @@ export { type InputResourceGroupProps } from '#ui/forms/InputResourceGroup' export { - HookedInputSelect, - InputSelect, flatSelectValues, getDefaultValueFromFlatten, + HookedInputSelect, + InputSelect, isGroupedSelectValues, isMultiValueSelected, isSingleValueSelected, @@ -317,9 +318,9 @@ export { } from '#ui/forms/ReactHookForm' // Resources export { + getResourceAddressFormFieldsSchema, ResourceAddress, ResourceAddressFormFields, - getResourceAddressFormFieldsSchema, useResourceAddressOverlay, type ResourceAddressFormFieldsProps, type ResourceAddressProps @@ -345,8 +346,8 @@ export { type ResourceOrderTimelineProps } from '#ui/resources/ResourceOrderTimeline' export { - ResourcePaymentMethod, getPaymentMethodLogoSrc, + ResourcePaymentMethod, type ResourcePaymentMethodProps } from '#ui/resources/ResourcePaymentMethod' export { @@ -392,3 +393,4 @@ export { getStockTransferDisplayStatus, getStockTransferStatusName } from '#dictionaries/stockTransfers' +export type { en as Locale } from './locales/en' diff --git a/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx index 8c06f934..644836ac 100644 --- a/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx @@ -7,6 +7,7 @@ import { type i18n as I18nInstance } from 'i18next' import React, { type ReactNode, useEffect, useState } from 'react' import { I18nextProvider } from 'react-i18next' +export { t } from 'i18next' export { useTranslation } from 'react-i18next' interface I18NProviderProps { diff --git a/packages/app-elements/src/providers/I18NProvider/i18n.ts b/packages/app-elements/src/providers/I18NProvider/i18n.ts index 722f5483..10318e5c 100644 --- a/packages/app-elements/src/providers/I18NProvider/i18n.ts +++ b/packages/app-elements/src/providers/I18NProvider/i18n.ts @@ -14,7 +14,7 @@ export const initI18n = async ( .use( resourcesToBackend( async (language: I18NLocale) => - await import(`../../locales/${language}.json`) + await import(`../../locales/${language}.ts`) ) ) .use(LanguageDetector) diff --git a/packages/app-elements/src/providers/I18NProvider/index.tsx b/packages/app-elements/src/providers/I18NProvider/index.tsx index e3daad63..760610ae 100644 --- a/packages/app-elements/src/providers/I18NProvider/index.tsx +++ b/packages/app-elements/src/providers/I18NProvider/index.tsx @@ -1,2 +1,2 @@ export { initI18n, languages, type I18NLocale } from './i18n' -export { I18NProvider, useTranslation } from './I18NProvider' +export { I18NProvider, t, useTranslation } from './I18NProvider' diff --git a/packages/app-elements/vite.config.js b/packages/app-elements/vite.config.js index f37fe2d1..ba9c5aa2 100644 --- a/packages/app-elements/vite.config.js +++ b/packages/app-elements/vite.config.js @@ -1,6 +1,8 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ // @ts-check import react from '@vitejs/plugin-react' -import { resolve } from 'path' +import fs from 'fs' +import path, { resolve } from 'path' import dts from 'vite-plugin-dts' import { defineConfig } from 'vitest/config' import pkg from './package.json' @@ -12,7 +14,8 @@ export default defineConfig({ dts({ insertTypesEntry: true, include: ['src'] - }) + }), + i18nTypes() ], build: { lib: { @@ -60,3 +63,29 @@ export default defineConfig({ ] } }) + +/** @typedef {import("vite").Plugin} Plugin */ +/** @typedef {import("vite").ResolvedConfig} ResolvedConfig */ + +function i18nTypes() { + /** @type {ResolvedConfig} */ + let config + + /** @type {Plugin} */ + const plugin = { + name: 'i18n-types', + apply: 'build', + async configResolved(_config) { + config = _config + }, + buildEnd() { + const origin = path.resolve(config.root, 'i18n.d.ts') + const dest = path.resolve(config.build.outDir, 'i18n.d.ts') + let content = fs.readFileSync(origin, 'utf-8') + content = content.replace('./src/locales/en', './locales/en') + fs.writeFileSync(dest, content) + console.log('i18n types copied') + } + } + return plugin +} From 1b2c50b8f453e0bb4a2630e7d33a71f6a4908a18 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:35:34 +0100 Subject: [PATCH 11/33] fix: update vite plugin hook --- packages/app-elements/vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-elements/vite.config.js b/packages/app-elements/vite.config.js index ba9c5aa2..44408e31 100644 --- a/packages/app-elements/vite.config.js +++ b/packages/app-elements/vite.config.js @@ -78,7 +78,7 @@ function i18nTypes() { async configResolved(_config) { config = _config }, - buildEnd() { + closeBundle() { const origin = path.resolve(config.root, 'i18n.d.ts') const dest = path.resolve(config.build.outDir, 'i18n.d.ts') let content = fs.readFileSync(origin, 'utf-8') From 92faaad701e67d7005cc726bc777fc43f24bc8b2 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:49:41 +0100 Subject: [PATCH 12/33] fix: apply new structure to locales --- packages/app-elements/i18n.d.ts | 11 - packages/app-elements/package.json | 3 - .../src/dictionaries/customers.ts | 18 +- .../app-elements/src/dictionaries/orders.ts | 94 ++++--- .../src/dictionaries/promotions.ts | 10 +- .../app-elements/src/dictionaries/returns.ts | 38 +-- .../src/dictionaries/shipments.ts | 48 ++-- .../src/dictionaries/stockTransfers.ts | 36 +-- .../src/hooks/useEditTagsOverlay.tsx | 4 +- packages/app-elements/src/locales/en.ts | 266 ++++++++++-------- packages/app-elements/src/locales/it.ts | 235 +++++++++------- packages/app-elements/src/main.ts | 4 +- .../{I18NProvider => }/I18NProvider.tsx | 49 +++- .../src/providers/I18NProvider/i18n.ts | 33 --- .../src/providers/I18NProvider/index.tsx | 2 - packages/app-elements/tsconfig.json | 3 +- packages/app-elements/vite.config.js | 32 +-- .../src/stories/examples/I18n.stories.tsx | 7 +- 18 files changed, 465 insertions(+), 428 deletions(-) delete mode 100644 packages/app-elements/i18n.d.ts rename packages/app-elements/src/providers/{I18NProvider => }/I18NProvider.tsx (50%) delete mode 100644 packages/app-elements/src/providers/I18NProvider/i18n.ts delete mode 100644 packages/app-elements/src/providers/I18NProvider/index.tsx diff --git a/packages/app-elements/i18n.d.ts b/packages/app-elements/i18n.d.ts deleted file mode 100644 index 2f23a94a..00000000 --- a/packages/app-elements/i18n.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import 'i18next' -import type translation from './src/locales/en' - -declare module 'i18next' { - interface CustomTypeOptions { - defaultNS: 'translation' - resources: { - translation: typeof translation - } - } -} diff --git a/packages/app-elements/package.json b/packages/app-elements/package.json index a0e3902d..17d93cf6 100644 --- a/packages/app-elements/package.json +++ b/packages/app-elements/package.json @@ -20,9 +20,6 @@ }, "./tailwind.config": { "require": "./dist/tailwind.config.js" - }, - "./i18n.d.ts": { - "require": "./dist/i18n.d.ts" } }, "engines": { diff --git a/packages/app-elements/src/dictionaries/customers.ts b/packages/app-elements/src/dictionaries/customers.ts index 8f58fff5..7c8236d9 100644 --- a/packages/app-elements/src/dictionaries/customers.ts +++ b/packages/app-elements/src/dictionaries/customers.ts @@ -10,35 +10,35 @@ export function getCustomerDisplayStatus( switch (customerObj.status) { case 'prospect': return { - label: t('common.resources.customers.status.prospect'), + label: t('resources.customers.attributes.status.prospect'), icon: 'chatCircle', color: 'orange', - task: t('common.resources.customers.status.prospect') + task: t('resources.customers.attributes.status.prospect') } case 'acquired': return { - label: t('common.resources.customers.status.acquired'), + label: t('resources.customers.attributes.status.acquired'), icon: 'check', color: 'orange', - task: t('common.resources.customers.status.acquired') + task: t('resources.customers.attributes.status.acquired') } case 'repeat': return { - label: t('common.resources.customers.status.repeat'), + label: t('resources.customers.attributes.status.repeat'), icon: 'arrowUpRight', color: 'orange', - task: t('common.resources.customers.status.repeat') + task: t('resources.customers.attributes.status.repeat') } } } export function getCustomerStatusName(status: Customer['status']): string { const dictionary: Record<typeof status, string> = { - prospect: t('common.resources.customers.status.prospect'), - acquired: t('common.resources.customers.status.acquired'), - repeat: t('common.resources.customers.status.repeat') + prospect: t('resources.customers.attributes.status.prospect'), + acquired: t('resources.customers.attributes.status.acquired'), + repeat: t('resources.customers.attributes.status.repeat') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index da0d6349..b986c72e 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -15,10 +15,10 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { if (order.status === 'editing') { return { - label: t('common.resources.orders.status.editing'), + label: t('resources.orders.attributes.status.editing'), icon: 'pencilSimple', color: 'orange', - task: t('common.resources.orders.status.editing') + task: t('resources.orders.attributes.status.editing') } } @@ -32,49 +32,49 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'placed:free:unfulfilled': case 'placed:free:not_required': return { - label: t('common.resources.orders.status.placed'), + label: t('resources.orders.attributes.status.placed'), icon: 'arrowDown', color: 'orange', - task: t('common.resources.orders.task.awaiting_approval') + task: t('apps.orders.task.awaiting_approval') } case 'placed:unpaid:unfulfilled': return { - label: t('common.resources.orders.status.placed'), + label: t('resources.orders.attributes.status.placed'), icon: 'x', color: 'red', - task: t('common.resources.orders.task.error_to_cancel') + task: t('apps.orders.task.error_to_cancel') } case 'approved:authorized:unfulfilled': case 'approved:authorized:not_required': return { - label: t('common.resources.orders.status.approved'), + label: t('resources.orders.attributes.status.approved'), icon: 'creditCard', color: 'orange', - task: t('common.resources.orders.task.payment_to_capture') + task: t('apps.orders.task.payment_to_capture') } case 'approved:paid:in_progress': case 'approved:partially_refunded:in_progress': return { - label: t('common.resources.orders.status.in_progress'), + label: t('resources.orders.attributes.status.in_progress'), icon: 'arrowClockwise', color: 'orange', - task: t('common.resources.orders.task.fulfillment_in_progress') + task: t('apps.orders.task.fulfillment_in_progress') } case 'approved:authorized:in_progress': return { - label: t('common.resources.orders.status.in_progress_manual'), + label: t('apps.orders.display_status.in_progress_manual'), icon: 'arrowClockwise', color: 'orange', - task: t('common.resources.orders.task.fulfillment_in_progress') + task: t('apps.orders.task.fulfillment_in_progress') } case 'approved:paid:fulfilled': return { - label: t('common.resources.orders.fulfillment_status.fulfilled'), + label: t('resources.orders.attributes.fulfillment_status.fulfilled'), icon: 'check', color: 'green' } @@ -82,7 +82,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { // TODO: This could be a gift-card and what If i do return? case 'approved:free:fulfilled': return { - label: t('common.resources.orders.fulfillment_status.fulfilled'), + label: t('resources.orders.attributes.fulfillment_status.fulfilled'), icon: 'check', color: 'green' } @@ -90,21 +90,23 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'approved:paid:not_required': case 'approved:partially_refunded:not_required': return { - label: t('common.resources.orders.status.approved'), + label: t('resources.orders.attributes.status.approved'), icon: 'check', color: 'green' } case 'approved:free:not_required': return { - label: t('common.resources.orders.status.approved'), + label: t('resources.orders.attributes.status.approved'), icon: 'check', color: 'green' } case 'approved:partially_refunded:fulfilled': return { - label: t('common.resources.orders.payment_status.partially_refunded'), + label: t( + 'resources.orders.attributes.payment_status.partially_refunded' + ), icon: 'check', color: 'green' } @@ -115,14 +117,14 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'cancelled:unpaid:unfulfilled': case 'cancelled:free:unfulfilled': return { - label: t('common.resources.orders.status.cancelled'), + label: t('resources.orders.attributes.status.cancelled'), icon: 'x', color: 'gray' } case 'cancelled:refunded:fulfilled': return { - label: t('common.resources.orders.status.cancelled'), + label: t('resources.orders.attributes.status.cancelled'), icon: 'x', color: 'gray' } @@ -131,14 +133,14 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'pending:authorized:unfulfilled': case 'pending:free:unfulfilled': return { - label: t('common.resources.orders.status.pending'), + label: t('resources.orders.attributes.status.pending'), icon: 'shoppingBag', color: 'white' } default: return { - label: `${t('common.resources.common.status.not_handled')}: (${combinedStatus})`, + label: `${t('common.not_handled')}: (${combinedStatus})`, icon: 'warning', color: 'white' } @@ -169,13 +171,13 @@ export function getOrderTransactionName( export function getOrderStatusName(status: Order['status']): string { const dictionary: Record<typeof status, string> = { - approved: t('common.resources.orders.status.approved'), - cancelled: t('common.resources.orders.status.cancelled'), - draft: t('common.resources.orders.status.draft'), - editing: t('common.resources.orders.status.editing'), - pending: t('common.resources.orders.status.pending'), - placed: t('common.resources.orders.status.placed'), - placing: t('common.resources.orders.status.placing') + approved: t('resources.orders.attributes.status.approved'), + cancelled: t('resources.orders.attributes.status.cancelled'), + draft: t('resources.orders.attributes.status.draft'), + editing: t('resources.orders.attributes.status.editing'), + pending: t('resources.orders.attributes.status.pending'), + placed: t('resources.orders.attributes.status.placed'), + placing: t('resources.orders.attributes.status.placing') } return dictionary[status] @@ -185,21 +187,23 @@ export function getOrderPaymentStatusName( status: Order['payment_status'] ): string { const dictionary: Record<typeof status, string> = { - authorized: t('common.resources.orders.payment_status.authorized'), - paid: t('common.resources.orders.payment_status.paid'), - unpaid: t('common.resources.orders.payment_status.unpaid'), - free: t('common.resources.orders.payment_status.free'), - voided: t('common.resources.orders.payment_status.voided'), - refunded: t('common.resources.orders.payment_status.refunded'), + authorized: t('resources.orders.attributes.payment_status.authorized'), + paid: t('resources.orders.attributes.payment_status.paid'), + unpaid: t('resources.orders.attributes.payment_status.unpaid'), + free: t('resources.orders.attributes.payment_status.free'), + voided: t('resources.orders.attributes.payment_status.voided'), + refunded: t('resources.orders.attributes.payment_status.refunded'), partially_authorized: t( - 'common.resources.orders.payment_status.partially_authorized' + 'resources.orders.attributes.payment_status.partially_authorized' + ), + partially_paid: t( + 'resources.orders.attributes.payment_status.partially_paid' ), - partially_paid: t('common.resources.orders.payment_status.partially_paid'), partially_refunded: t( - 'common.resources.orders.payment_status.partially_refunded' + 'resources.orders.attributes.payment_status.partially_refunded' ), partially_voided: t( - 'common.resources.orders.payment_status.partially_voided' + 'resources.orders.attributes.payment_status.partially_voided' ) } @@ -210,10 +214,16 @@ export function getOrderFulfillmentStatusName( status: Order['fulfillment_status'] ): string { const dictionary: Record<typeof status, string> = { - unfulfilled: t('common.resources.orders.fulfillment_status.unfulfilled'), - in_progress: t('common.resources.orders.fulfillment_status.in_progress'), - fulfilled: t('common.resources.orders.fulfillment_status.fulfilled'), - not_required: t('common.resources.orders.fulfillment_status.not_required') + unfulfilled: t( + 'resources.orders.attributes.fulfillment_status.unfulfilled' + ), + in_progress: t( + 'resources.orders.attributes.fulfillment_status.in_progress' + ), + fulfilled: t('resources.orders.attributes.fulfillment_status.fulfilled'), + not_required: t( + 'resources.orders.attributes.fulfillment_status.not_required' + ) } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/promotions.ts b/packages/app-elements/src/dictionaries/promotions.ts index 85d5824a..8690468f 100644 --- a/packages/app-elements/src/dictionaries/promotions.ts +++ b/packages/app-elements/src/dictionaries/promotions.ts @@ -13,7 +13,7 @@ export function getPromotionDisplayStatus( if (promotion.disabled_at != null) { return { status: 'disabled', - label: t('common.resources.promotions.status.disabled'), + label: t('resources.promotions.attributes.status.disabled'), icon: 'minus', color: 'lightGray' } @@ -30,7 +30,7 @@ export function getPromotionDisplayStatus( ) { return { status: 'used', - label: t('common.resources.promotions.status.expired'), + label: t('resources.promotions.attributes.status.expired'), icon: 'flag', color: 'gray' } @@ -40,7 +40,7 @@ export function getPromotionDisplayStatus( case 'past': return { status: 'expired', - label: t('common.resources.promotions.status.expired'), + label: t('resources.promotions.attributes.status.expired'), icon: 'flag', color: 'gray' } @@ -48,7 +48,7 @@ export function getPromotionDisplayStatus( case 'upcoming': return { status: 'upcoming', - label: t('common.resources.promotions.status.upcoming'), + label: t('resources.promotions.attributes.status.upcoming'), icon: 'calendarBlank', color: 'gray' } @@ -56,7 +56,7 @@ export function getPromotionDisplayStatus( case 'active': return { status: 'active', - label: t('common.resources.promotions.status.active'), + label: t('resources.promotions.attributes.status.active'), icon: 'pulse', color: 'green' } diff --git a/packages/app-elements/src/dictionaries/returns.ts b/packages/app-elements/src/dictionaries/returns.ts index c9546dee..16c047b7 100644 --- a/packages/app-elements/src/dictionaries/returns.ts +++ b/packages/app-elements/src/dictionaries/returns.ts @@ -14,59 +14,59 @@ export function getReturnDisplayStatus(returnObj: Return): ReturnDisplayStatus { switch (returnObj.status) { case 'requested': return { - label: t('common.resources.returns.status.requested'), + label: t('resources.returns.attributes.status.requested'), icon: 'chatCircle', color: 'orange', - task: t('common.resources.returns.status.requested') + task: t('resources.returns.attributes.status.requested') } case 'approved': return { - label: t('common.resources.returns.status.approved'), + label: t('resources.returns.attributes.status.approved'), icon: 'check', color: 'orange', - task: t('common.resources.returns.status.approved') + task: t('resources.returns.attributes.status.approved') } case 'shipped': return { - label: t('common.resources.returns.status.shipped'), + label: t('resources.returns.attributes.status.shipped'), icon: 'arrowUpRight', color: 'orange', - task: t('common.resources.returns.status.shipped') + task: t('resources.returns.attributes.status.shipped') } case 'received': return { - label: t('common.resources.returns.status.received'), + label: t('resources.returns.attributes.status.received'), icon: 'check', color: 'green' } case 'cancelled': return { - label: t('common.resources.returns.status.cancelled'), + label: t('resources.returns.attributes.status.cancelled'), icon: 'x', color: 'gray' } case 'rejected': return { - label: t('common.resources.returns.status.rejected'), + label: t('resources.returns.attributes.status.rejected'), icon: 'x', color: 'red' } case 'refunded': return { - label: t('common.resources.returns.status.refunded'), + label: t('resources.returns.attributes.status.refunded'), icon: 'creditCard', color: 'green' } default: return { - label: `${t('common.resources.common.status.not_handled')}: (${returnObj.status})`, + label: `${t('common.not_handled')}: (${returnObj.status})`, icon: 'warning', color: 'white' } @@ -75,14 +75,14 @@ export function getReturnDisplayStatus(returnObj: Return): ReturnDisplayStatus { export function getReturnStatusName(status: Return['status']): string { const dictionary: Record<typeof status, string> = { - draft: t('common.resources.returns.status.draft'), - requested: t('common.resources.returns.status.requested'), - approved: t('common.resources.returns.status.approved'), - shipped: t('common.resources.returns.status.shipped'), - received: t('common.resources.returns.status.received'), - cancelled: t('common.resources.returns.status.cancelled'), - rejected: t('common.resources.returns.status.rejected'), - refunded: t('common.resources.returns.status.refunded') + draft: t('resources.returns.attributes.status.draft'), + requested: t('resources.returns.attributes.status.requested'), + approved: t('resources.returns.attributes.status.approved'), + shipped: t('resources.returns.attributes.status.shipped'), + received: t('resources.returns.attributes.status.received'), + cancelled: t('resources.returns.attributes.status.cancelled'), + rejected: t('resources.returns.attributes.status.rejected'), + refunded: t('resources.returns.attributes.status.refunded') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/shipments.ts b/packages/app-elements/src/dictionaries/shipments.ts index 52fee2cd..ce3325ac 100644 --- a/packages/app-elements/src/dictionaries/shipments.ts +++ b/packages/app-elements/src/dictionaries/shipments.ts @@ -21,74 +21,74 @@ export function getShipmentDisplayStatus( switch (shipmentStatus) { case 'upcoming': return { - label: t('common.resources.shipments.status.upcoming'), + label: t('resources.shipments.attributes.status.upcoming'), icon: 'truck', color: 'gray' } case 'cancelled': return { - label: t('common.resources.shipments.status.cancelled'), + label: t('resources.shipments.attributes.status.cancelled'), icon: 'x', color: 'gray' } case 'draft': return { - label: t('common.resources.shipments.status.draft'), + label: t('resources.shipments.attributes.status.draft'), icon: 'minus', color: 'gray' } case 'on_hold': return { - label: t('common.resources.shipments.status.on_hold'), + label: t('resources.shipments.attributes.status.on_hold'), icon: 'hourglass', color: 'orange', - task: t('common.resources.shipments.status.on_hold') + task: t('resources.shipments.attributes.status.on_hold') } case 'packing': return { - label: t('common.resources.shipments.status.packing'), + label: t('resources.shipments.attributes.status.packing'), icon: 'package', color: 'orange', - task: t('common.resources.shipments.status.packing') + task: t('resources.shipments.attributes.status.packing') } case 'picking': return { - label: t('common.resources.shipments.status.picking'), + label: t('resources.shipments.attributes.status.picking'), icon: 'arrowDown', color: 'orange', - task: t('common.resources.shipments.status.picking') + task: t('resources.shipments.attributes.status.picking') } case 'ready_to_ship': return { - label: t('common.resources.shipments.status.ready_to_ship'), + label: t('resources.shipments.attributes.status.ready_to_ship'), icon: 'arrowUpRight', color: 'orange', - task: t('common.resources.shipments.status.ready_to_ship') + task: t('resources.shipments.attributes.status.ready_to_ship') } case 'shipped': return { - label: t('common.resources.shipments.status.shipped'), + label: t('resources.shipments.attributes.status.shipped'), icon: 'arrowUpRight', color: 'green' } case 'delivered': return { - label: t('common.resources.shipments.status.delivered'), + label: t('resources.shipments.attributes.status.delivered'), icon: 'check', color: 'green' } case 'awaiting_stock_transfer': return { - label: t('common.resources.shipments.status.awaiting_stock_transfer'), + label: t('apps.shipments.details.awaiting_stock_transfer'), icon: 'hourglass', color: 'orange', task: 'Awaiting stock transfers' @@ -96,7 +96,7 @@ export function getShipmentDisplayStatus( default: return { - label: `${t('common.resources.common.status.not_handled')}: (${shipment.status})`, + label: `${t('common.not_handled')}: (${shipment.status})`, icon: 'warning', color: 'white' } @@ -105,15 +105,15 @@ export function getShipmentDisplayStatus( export function getShipmentStatusName(status: Shipment['status']): string { const dictionary: Record<typeof status, string> = { - draft: t('common.resources.shipments.status.draft'), - on_hold: t('common.resources.shipments.status.on_hold'), - upcoming: t('common.resources.shipments.status.upcoming'), - packing: t('common.resources.shipments.status.packing'), - picking: t('common.resources.shipments.status.picking'), - ready_to_ship: t('common.resources.shipments.status.ready_to_ship'), - shipped: t('common.resources.shipments.status.shipped'), - cancelled: t('common.resources.shipments.status.cancelled'), - delivered: t('common.resources.shipments.status.delivered') + draft: t('resources.shipments.attributes.status.draft'), + on_hold: t('resources.shipments.attributes.status.on_hold'), + upcoming: t('resources.shipments.attributes.status.upcoming'), + packing: t('resources.shipments.attributes.status.packing'), + picking: t('resources.shipments.attributes.status.picking'), + ready_to_ship: t('resources.shipments.attributes.status.ready_to_ship'), + shipped: t('resources.shipments.attributes.status.shipped'), + cancelled: t('resources.shipments.attributes.status.cancelled'), + delivered: t('resources.shipments.attributes.status.delivered') } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/stockTransfers.ts b/packages/app-elements/src/dictionaries/stockTransfers.ts index efbd22e5..476abc32 100644 --- a/packages/app-elements/src/dictionaries/stockTransfers.ts +++ b/packages/app-elements/src/dictionaries/stockTransfers.ts @@ -16,53 +16,53 @@ export function getStockTransferDisplayStatus( switch (stockTransfer.status) { case 'upcoming': return { - label: t('common.resources.stock_transfers.status.upcoming'), + label: t('resources.stock_transfers.attributes.status.upcoming'), icon: 'arrowUpRight', color: 'orange', - task: t('common.resources.stock_transfers.status.upcoming') + task: t('resources.stock_transfers.attributes.status.upcoming') } case 'on_hold': return { - label: t('common.resources.stock_transfers.status.on_hold'), + label: t('resources.stock_transfers.attributes.status.on_hold'), icon: 'hourglass', color: 'orange', - task: t('common.resources.stock_transfers.status.on_hold') + task: t('resources.stock_transfers.attributes.status.on_hold') } case 'picking': return { - label: t('common.resources.stock_transfers.status.picking'), + label: t('resources.stock_transfers.attributes.status.picking'), icon: 'arrowDown', color: 'orange', - task: t('common.resources.stock_transfers.status.picking') + task: t('resources.stock_transfers.attributes.status.picking') } case 'in_transit': return { - label: t('common.resources.stock_transfers.status.in_transit'), + label: t('resources.stock_transfers.attributes.status.in_transit'), icon: 'arrowsLeftRight', color: 'orange', - task: t('common.resources.stock_transfers.status.in_transit') + task: t('resources.stock_transfers.attributes.status.in_transit') } case 'completed': return { - label: t('common.resources.stock_transfers.status.completed'), + label: t('resources.stock_transfers.attributes.status.completed'), icon: 'check', color: 'green' } case 'cancelled': return { - label: t('common.resources.stock_transfers.status.cancelled'), + label: t('resources.stock_transfers.attributes.status.cancelled'), icon: 'x', color: 'gray' } default: return { - label: `${t('common.resources.common.status.not_handled')}: (${stockTransfer.status})`, + label: `${t('common.not_handled')}: (${stockTransfer.status})`, icon: 'warning', color: 'white' } @@ -73,13 +73,13 @@ export function getStockTransferStatusName( status: StockTransfer['status'] ): string { const dictionary: Record<typeof status, string> = { - cancelled: t('common.resources.stock_transfers.status.cancelled'), - completed: t('common.resources.stock_transfers.status.completed'), - draft: t('common.resources.stock_transfers.status.draft'), - in_transit: t('common.resources.stock_transfers.status.in_transit'), - on_hold: t('common.resources.stock_transfers.status.on_hold'), - picking: t('common.resources.stock_transfers.status.picking'), - upcoming: t('common.resources.stock_transfers.status.upcoming') + cancelled: t('resources.stock_transfers.attributes.status.cancelled'), + completed: t('resources.stock_transfers.attributes.status.completed'), + draft: t('resources.stock_transfers.attributes.status.draft'), + in_transit: t('resources.stock_transfers.attributes.status.in_transit'), + on_hold: t('resources.stock_transfers.attributes.status.on_hold'), + picking: t('resources.stock_transfers.attributes.status.picking'), + upcoming: t('resources.stock_transfers.attributes.status.upcoming') } return dictionary[status] diff --git a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx index f6bcb69f..0b3133f1 100644 --- a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx +++ b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx @@ -54,7 +54,7 @@ export function useEditTagsOverlay(): TagsOverlayHook { } }) - const resourceName = t('common.resources.tags.name_other') + const resourceName = t('resources.tags.name_other') return { show: open, @@ -156,7 +156,7 @@ export function useEditTagsOverlay(): TagsOverlayHook { showManageAction != null && showManageAction ? [ { - label: t('common.manage', { + label: t('common.manage_resource', { resource: resourceName.toLowerCase() }), variant: 'secondary', diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 34be0b98..d871f196 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -1,56 +1,77 @@ -export const en = { +import { + type ListableResourceType, + type ResourceFields +} from '@commercelayer/sdk' + +const resources = { + orders: { + name: 'Address', + name_other: 'Addresses', + attributes: { + number: 'numero ordine' + } + }, + customers: { + name: 'Customer', + name_other: 'Customers', + attributes: { + email: 'indirizzo mail' + } + } +} satisfies { + [key in ListableResourceType]?: { + name: string + name_other: string + attributes: { + [attr in keyof ResourceFields[key]]?: string + } + } +} + +const en = { common: { - all: 'All {{resource}}', - all_female: 'All {{resource}}', - not_handled: 'Not handled', + add_up_to: 'You can add up to {{limit}} {{resource}}.', + all_items: 'All items', back: 'Back', - new: 'New', - new_resource: 'New {{resource}}', - new_resource_female: 'New {{resource}}', - not_authorized: 'Not authorized', - no_items: 'No items', edit: 'Edit', - manage: 'Manage {{resource}}', - updated: 'Updated', - timeline: 'Timeline', filters: 'Filters', + limit_reached: 'Limit reached', + manage_resource: 'Manage {{resource}}', metadata: 'Metadata', + new: 'New', + no_items: 'No items', + not_authorized: 'Not authorized', + not_handled: 'Not handled', search: 'Search...', - limit_reached: 'Limit reached', - add_up_to: 'You can add up to {{limit}} {{resource}}.', + timeline: 'Timeline', update: 'Update', - resources: { - common: { - status: { - not_handled: 'Not handled' - } - }, - adjustments: { - name: 'Adjustment', - name_other: 'Adjustments', - task: { - adjust_total: 'Adjust total' - } - }, - bundles: { - name: 'Bundle', - name_other: 'Bundles', - status: {} - }, - customers: { - name: 'Customer', - name_other: 'Customers', + updated: 'Updated' + }, + resources: { + adjustments: { + name: 'Adjustment', + name_other: 'Adjustments' + }, + bundles: { + name: 'Bundle', + name_other: 'Bundles' + }, + customers: { + name: 'Customer', + name_other: 'Customers', + attributes: { status: { prospect: 'Prospect', acquired: 'Acquired', repeat: 'Repeat' } - }, - orders: { - name: 'Order', - name_other: 'Orders', + } + }, + orders: { + name: 'Order', + name_other: 'Orders', + attributes: { status: { - name: 'Status', approved: 'Approved', cancelled: 'Cancelled', draft: 'Draft', @@ -58,12 +79,9 @@ export const en = { pending: 'Pending', placed: 'Placed', placing: 'Placing', - in_progress: 'In progress', - in_progress_manual: 'In progress (Manual)' + in_progress: 'In progress' }, payment_status: { - name: 'Payment status', - name_short: 'Payment', authorized: 'Authorized', paid: 'Paid', unpaid: 'Unpaid', @@ -76,58 +94,23 @@ export const en = { partially_voided: 'Part. voided' }, fulfillment_status: { - name: 'Fulfillment status', - name_short: 'Fulfillment', unfulfilled: 'Unfulfilled', in_progress: 'In progress', fulfilled: 'Fulfilled', not_required: 'Not required' }, - task: { - open: 'Open', - browse: 'Browse', - awaiting_approval: 'Awaiting approval', - error_to_cancel: 'Error to cancel', - payment_to_capture: 'Payment to capture', - fulfillment_in_progress: 'Fulfillment in progress', - editing: 'Editing', - history: 'Order history', - carts: 'Carts', - archived: 'Archived' - }, - details: { - summary: 'Summary', - to_be_calculated: 'To be calculated', - shipping: 'Shipping', - subtotal: 'Subtotal', - total: 'Total', - payment_method: 'Payment method', - taxes: 'Taxes', - included: 'included', - discount: 'Discount', - billing_address: 'Billing address', - shipping_address: 'Shipping address' - }, - actions: { - add_item: 'Add item', - approve: 'Approve', - archive: 'Archive', - cancel_transactions: 'Cancel payment', - cancel: 'Cancel order', - capture: 'Capture payment', - place: 'Place order', - refund: 'Refund', - unarchive: 'Unarchive', - select_address: 'Select address' - } - }, - gift_cards: { - name: 'Gift card', - name_other: 'Gift cards' - }, - promotions: { - name: 'Promotion', - name_other: 'Promotions', + billing_address: 'Billing address', + shipping_address: 'Shipping address' + } + }, + gift_cards: { + name: 'Gift card', + name_other: 'Gift cards' + }, + promotions: { + name: 'Promotion', + name_other: 'Promotions', + attributes: { status: { active: 'Active', disabled: 'Disabled', @@ -136,10 +119,12 @@ export const en = { pending: 'Pending', upcoming: 'Upcoming' } - }, - returns: { - name: 'Return', - name_other: 'Returns', + } + }, + returns: { + name: 'Return', + name_other: 'Returns', + attributes: { status: { approved: 'Approved', cancelled: 'Cancelled', @@ -150,12 +135,13 @@ export const en = { refunded: 'Refunded', shipped: 'Shipped' } - }, - shipments: { - name: 'Shipment', - name_other: 'Shipments', + } + }, + shipments: { + name: 'Shipment', + name_other: 'Shipments', + attributes: { status: { - awaiting_stock_transfer: 'Awaiting stock transfer', cancelled: 'Cancelled', delivered: 'Delivered', draft: 'Draft', @@ -166,10 +152,12 @@ export const en = { shipped: 'Shipped', upcoming: 'Upcoming' } - }, - stock_transfers: { - name: 'Stock transfer', - name_other: 'Stock transfers', + } + }, + stock_transfers: { + name: 'Stock transfer', + name_other: 'Stock transfers', + attributes: { status: { cancelled: 'Cancelled', completed: 'Completed', @@ -179,17 +167,73 @@ export const en = { picking: 'Picking', upcoming: 'Upcoming' } + } + }, + tags: { + name: 'Tag', + name_other: 'Tags' + } + }, + apps: { + orders: { + attributes: { + status: 'Status', + payment_status: 'Payment status', + fulfillment_status: 'Fulfillment status' + }, + display_status: { + in_progress: 'In progress', + in_progress_manual: 'In progress (Manual)' + }, + task: { + open: 'Open', + browse: 'Browse', + awaiting_approval: 'Awaiting approval', + error_to_cancel: 'Error to cancel', + payment_to_capture: 'Payment to capture', + fulfillment_in_progress: 'Fulfillment in progress', + editing: 'Editing', + history: 'Order history', + carts: 'Carts', + archived: 'Archived' }, - tags: { - name: 'Tag', - name_other: 'Tags', - status: {} + details: { + summary: 'Summary', + to_be_calculated: 'To be calculated', + shipping: 'Shipping', + subtotal: 'Subtotal', + total: 'Total', + payment_method: 'Payment method', + taxes: 'Taxes', + included: 'included', + discount: 'Discount', + fulfillment: 'Fulfillment', + payment: 'Payment', + adjust_total: 'Adjust total' + }, + actions: { + add_item: 'Add item', + approve: 'Approve', + archive: 'Archive', + cancel_transactions: 'Cancel payment', + cancel: 'Cancel order', + capture: 'Capture payment', + place: 'Place order', + refund: 'Refund', + unarchive: 'Unarchive', + select_address: 'Select address' } }, - validation: { - select_one_item: 'Please select at least one item' + shipments: { + details: { + awaiting_stock_transfer: 'Awaiting stock transfer' + } } - } + }, + validation: { + select_one_item: 'Please select at least one item' + }, + res: resources } export default en diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 79c283ad..d9f44266 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -1,18 +1,15 @@ -import type { en } from './en' +import type en from './en' const it: typeof en = { common: { - all: 'Tutti {{resource}}', - all_female: 'Tutte {{resource}}', + all_items: 'Tutti gli elementi', not_handled: 'Non gestito', back: 'Torna indietro', new: 'Nuovo', - new_resource: 'Nuovo {{resource}}', - new_resource_female: 'Nuova {{resource}}', not_authorized: 'Non autorizzato', no_items: 'Nessun elemento', edit: 'Modifica', - manage: 'Gestisci {{resource}}', + manage_resource: 'Gestisci {{resource}}', updated: 'Aggiornato', timeline: 'Storico', filters: 'Filtri', @@ -20,39 +17,33 @@ const it: typeof en = { search: 'Cerca...', limit_reached: 'Limite raggiunto', add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', - update: 'Aggiorna', - resources: { - common: { - status: { - not_handled: 'Non gestito' - } - }, - adjustments: { - name: 'Rettifica', - name_other: 'Rettifiche', - task: { - adjust_total: 'Rettifica totale' - } - }, - bundles: { - name: 'Bundle', - name_other: 'Bundles', - status: {} - }, - customers: { - name: 'Cliente', - name_other: 'Clienti', + update: 'Aggiorna' + }, + resources: { + adjustments: { + name: 'Rettifica', + name_other: 'Rettifiche' + }, + bundles: { + name: 'Bundle', + name_other: 'Bundles' + }, + customers: { + name: 'Cliente', + name_other: 'Clienti', + attributes: { status: { prospect: 'Potenziale', acquired: 'Acquisito', repeat: 'Abituale' } - }, - orders: { - name: 'Ordine', - name_other: 'Ordini', + } + }, + orders: { + name: 'Ordine', + name_other: 'Ordini', + attributes: { status: { - name: 'Stato', approved: 'Approvato', cancelled: 'Cancellato', draft: 'Bozza', @@ -60,12 +51,9 @@ const it: typeof en = { pending: 'In attesa', placed: 'Piazzato', placing: 'In piazzamento', - in_progress: 'In corso', - in_progress_manual: 'In corso (Manuale)' + in_progress: 'In corso' }, payment_status: { - name: 'Stato pagamento', - name_short: 'Pagamento', authorized: 'Autorizzato', paid: 'Pagato', unpaid: 'Non pagato', @@ -78,58 +66,23 @@ const it: typeof en = { partially_voided: 'Parz. annullato' }, fulfillment_status: { - name: 'Stato evasione', - name_short: 'Evasione', unfulfilled: 'Non evaso', in_progress: 'In corso', fulfilled: 'Evaso', not_required: 'Non richiesto' }, - task: { - open: 'Aperti', - browse: 'Sfoglia', - awaiting_approval: 'In attesa di approvazione', - error_to_cancel: 'Errore nella cancellazione', - payment_to_capture: 'Pagamento da catturare', - fulfillment_in_progress: 'Evasione in corso', - editing: 'In modifica', - history: 'Tutti gli ordini', - carts: 'Carrelli', - archived: 'Archiviati' - }, - details: { - summary: 'Riepilogo', - to_be_calculated: 'Da calcolare', - shipping: 'Spedizione', - subtotal: 'Totale parziale', - total: 'Totale', - payment_method: 'Metodo di pagamento', - taxes: 'Tasse', - included: 'incluse', - discount: 'Sconto', - billing_address: 'Indirizzo di fatturazione', - shipping_address: 'Indirizzo di spedizione' - }, - actions: { - add_item: 'Aggiungi prodotto', - approve: 'Approva', - archive: 'Archivia', - cancel_transactions: 'Annulla pagamento', - cancel: 'Annulla ordine', - capture: 'Cattura pagamento', - place: 'Piazza ordine', - refund: 'Rimborsa', - unarchive: 'Ripristina', - select_address: 'Seleziona indirizzo' - } - }, - gift_cards: { - name: 'Carta regalo', - name_other: 'Carte regalo' - }, - promotions: { - name: 'Promozione', - name_other: 'Promozioni', + billing_address: 'Indirizzo di fatturazione', + shipping_address: 'Indirizzo di spedizione' + } + }, + gift_cards: { + name: 'Carta regalo', + name_other: 'Carte regalo' + }, + promotions: { + name: 'Promozione', + name_other: 'Promozioni', + attributes: { status: { active: 'Attiva', disabled: 'Disabilitata', @@ -138,10 +91,12 @@ const it: typeof en = { pending: 'In attesa', upcoming: 'Imminente' } - }, - returns: { - name: 'Reso', - name_other: 'Resi', + } + }, + returns: { + name: 'Reso', + name_other: 'Resi', + attributes: { status: { approved: 'Approvato', cancelled: 'Cancellato', @@ -152,12 +107,13 @@ const it: typeof en = { rejected: 'Rifiutato', shipped: 'Spedito' } - }, - shipments: { - name: 'Spedizione', - name_other: 'Spedizioni', + } + }, + shipments: { + name: 'Spedizione', + name_other: 'Spedizioni', + attributes: { status: { - awaiting_stock_transfer: 'In attesa di trasferimento di magazzino', cancelled: 'Cancellato', delivered: 'Consegnato', draft: 'Bozza', @@ -168,10 +124,12 @@ const it: typeof en = { shipped: 'Spedito', upcoming: 'Imminente' } - }, - stock_transfers: { - name: 'Trasferimento di magazzino', - name_other: 'Trasferimenti di magazzino', + } + }, + stock_transfers: { + name: 'Trasferimento di magazzino', + name_other: 'Trasferimenti di magazzino', + attributes: { status: { cancelled: 'Cancellato', completed: 'Completato', @@ -181,15 +139,86 @@ const it: typeof en = { picking: 'In prelievo', upcoming: 'Imminente' } + } + }, + tags: { + name: 'Tag', + name_other: 'Tag' + } + }, + validation: { + select_one_item: 'Seleziona almeno un elemento' + }, + res: { + orders: { + name: '', + name_other: '', + attributes: { + number: '' + } + }, + customers: { + name: '', + name_other: '', + attributes: { + email: '' + } + } + }, + apps: { + orders: { + attributes: { + status: 'Stato', + payment_status: 'Stato pagamento', + fulfillment_status: 'Stato evasione' + }, + display_status: { + in_progress: 'In corso', + in_progress_manual: 'In corso (Manuale)' + }, + task: { + open: 'Aperti', + browse: 'Sfoglia', + awaiting_approval: 'In attesa di approvazione', + error_to_cancel: 'Errore nella cancellazione', + payment_to_capture: 'Pagamento da catturare', + fulfillment_in_progress: 'Evasione in corso', + editing: 'In modifica', + history: 'Tutti gli ordini', + carts: 'Carrelli', + archived: 'Archiviati' }, - tags: { - name: 'Tag', - name_other: 'Tag', - status: {} + details: { + summary: 'Riepilogo', + to_be_calculated: 'Da calcolare', + shipping: 'Spedizione', + subtotal: 'Totale parziale', + total: 'Totale', + payment_method: 'Metodo di pagamento', + taxes: 'Tasse', + included: 'incluse', + discount: 'Sconto', + fulfillment: 'Evasione', + payment: 'Pagamento', + adjust_total: 'Aggiusta totale' + }, + actions: { + add_item: 'Aggiungi prodotto', + approve: 'Approva', + archive: 'Archivia', + cancel_transactions: 'Annulla pagamento', + cancel: 'Annulla ordine', + capture: 'Cattura pagamento', + place: 'Piazza ordine', + refund: 'Rimborsa', + unarchive: 'Ripristina', + select_address: 'Seleziona indirizzo' } }, - validation: { - select_one_item: 'Seleziona almeno un elemento' + shipments: { + details: { + awaiting_stock_transfer: 'In attesa di trasferimento di magazzino' + } } } } diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index 2958fbdf..07dc1386 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -62,9 +62,8 @@ export { createApp, type ClAppKey, type ClAppProps } from '#providers/createApp' export { ErrorBoundary } from '#providers/ErrorBoundary' export { GTMProvider, useTagManager } from '#providers/GTMProvider' export { + i18nLocales, I18NProvider, - initI18n, - languages, t, useTranslation, type I18NLocale @@ -393,4 +392,3 @@ export { getStockTransferDisplayStatus, getStockTransferStatusName } from '#dictionaries/stockTransfers' -export type { en as Locale } from './locales/en' diff --git a/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx similarity index 50% rename from packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx rename to packages/app-elements/src/providers/I18NProvider.tsx index 644836ac..32eabc8d 100644 --- a/packages/app-elements/src/providers/I18NProvider/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -1,22 +1,32 @@ -import { - type I18NLocale, - initI18n, - languages -} from '#providers/I18NProvider/i18n' -import { type i18n as I18nInstance } from 'i18next' +import i18n, { type i18n as I18nInstance } from 'i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import resourcesToBackend from 'i18next-resources-to-backend' import React, { type ReactNode, useEffect, useState } from 'react' -import { I18nextProvider } from 'react-i18next' +import { I18nextProvider, initReactI18next } from 'react-i18next' +import type en from '../locales/en' export { t } from 'i18next' export { useTranslation } from 'react-i18next' +export const i18nLocales = ['en', 'it'] as const +export type I18NLocale = (typeof i18nLocales)[number] + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: 'translation' + resources: { + translation: typeof en + } + } +} + interface I18NProviderProps { localeCode?: I18NLocale children: ReactNode } export const I18NProvider: React.FC<I18NProviderProps> = ({ - localeCode = languages[0], + localeCode = i18nLocales[0], children }) => { const [i18nInstance, setI18nInstance] = useState<I18nInstance | undefined>() @@ -50,3 +60,26 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ return <I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider> } + +const initI18n = async (localeCode: I18NLocale): Promise<I18nInstance> => { + await i18n + .use( + resourcesToBackend( + async (language: I18NLocale) => + await import(`../../locales/${language}.ts`) + ) + ) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + load: 'languageOnly', + supportedLngs: i18nLocales, + lng: localeCode, + fallbackLng: i18nLocales[0], + react: { + useSuspense: true + }, + debug: true + }) + return i18n +} diff --git a/packages/app-elements/src/providers/I18NProvider/i18n.ts b/packages/app-elements/src/providers/I18NProvider/i18n.ts deleted file mode 100644 index 10318e5c..00000000 --- a/packages/app-elements/src/providers/I18NProvider/i18n.ts +++ /dev/null @@ -1,33 +0,0 @@ -import i18n, { type i18n as I18nInstance } from 'i18next' -import LanguageDetector from 'i18next-browser-languagedetector' -import resourcesToBackend from 'i18next-resources-to-backend' -import { initReactI18next } from 'react-i18next' - -export const languages = ['en', 'it'] as const - -export type I18NLocale = (typeof languages)[number] - -export const initI18n = async ( - localeCode: I18NLocale -): Promise<I18nInstance> => { - await i18n - .use( - resourcesToBackend( - async (language: I18NLocale) => - await import(`../../locales/${language}.ts`) - ) - ) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - load: 'languageOnly', - supportedLngs: languages, - lng: localeCode, - fallbackLng: languages[0], - react: { - useSuspense: true - }, - debug: true - }) - return i18n -} diff --git a/packages/app-elements/src/providers/I18NProvider/index.tsx b/packages/app-elements/src/providers/I18NProvider/index.tsx deleted file mode 100644 index 760610ae..00000000 --- a/packages/app-elements/src/providers/I18NProvider/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { initI18n, languages, type I18NLocale } from './i18n' -export { I18NProvider, t, useTranslation } from './I18NProvider' diff --git a/packages/app-elements/tsconfig.json b/packages/app-elements/tsconfig.json index 74e71d38..3630ac43 100644 --- a/packages/app-elements/tsconfig.json +++ b/packages/app-elements/tsconfig.json @@ -53,8 +53,7 @@ "vite.config.js", "src", "mocks", - ".eslintrc.cjs", - "i18n.d.ts" + ".eslintrc.cjs" ], "references": [ { diff --git a/packages/app-elements/vite.config.js b/packages/app-elements/vite.config.js index 44408e31..3f88a5f1 100644 --- a/packages/app-elements/vite.config.js +++ b/packages/app-elements/vite.config.js @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ // @ts-check import react from '@vitejs/plugin-react' -import fs from 'fs' -import path, { resolve } from 'path' +import { resolve } from 'path' import dts from 'vite-plugin-dts' import { defineConfig } from 'vitest/config' import pkg from './package.json' @@ -14,8 +13,7 @@ export default defineConfig({ dts({ insertTypesEntry: true, include: ['src'] - }), - i18nTypes() + }) ], build: { lib: { @@ -63,29 +61,3 @@ export default defineConfig({ ] } }) - -/** @typedef {import("vite").Plugin} Plugin */ -/** @typedef {import("vite").ResolvedConfig} ResolvedConfig */ - -function i18nTypes() { - /** @type {ResolvedConfig} */ - let config - - /** @type {Plugin} */ - const plugin = { - name: 'i18n-types', - apply: 'build', - async configResolved(_config) { - config = _config - }, - closeBundle() { - const origin = path.resolve(config.root, 'i18n.d.ts') - const dest = path.resolve(config.build.outDir, 'i18n.d.ts') - let content = fs.readFileSync(origin, 'utf-8') - content = content.replace('./src/locales/en', './locales/en') - fs.writeFileSync(dest, content) - console.log('i18n types copied') - } - } - return plugin -} diff --git a/packages/docs/src/stories/examples/I18n.stories.tsx b/packages/docs/src/stories/examples/I18n.stories.tsx index aa4b5cb1..5863594a 100644 --- a/packages/docs/src/stories/examples/I18n.stories.tsx +++ b/packages/docs/src/stories/examples/I18n.stories.tsx @@ -61,13 +61,14 @@ export const Default: StoryFn = (): JSX.Element => { /> <Spacer top='4'> <Text> - Translation of string <strong>common.all</strong>: {t('common.all')} + Translation of string <strong>common.all_items</strong>:{' '} + {t('common.all_items')} </Text> </Spacer> <Spacer top='4'> <Text> - Translation of string <strong>common.all_female</strong>:{' '} - {t('common.all_female')} + Translation of string <strong>common.search</strong>:{' '} + {t('common.search')} </Text> </Spacer> </PageLayout> From f08f848cd4a55176bcbea5ad7032a44ef8bbd381 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:06:28 +0100 Subject: [PATCH 13/33] fix: add static type defs for locales --- .../app-elements/src/dictionaries/orders.ts | 2 +- .../src/dictionaries/promotions.ts | 2 +- packages/app-elements/src/locales/en.ts | 278 +++++++++--------- packages/app-elements/src/locales/it.ts | 39 +-- 4 files changed, 158 insertions(+), 163 deletions(-) diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index b986c72e..158d23dc 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -58,7 +58,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { case 'approved:paid:in_progress': case 'approved:partially_refunded:in_progress': return { - label: t('resources.orders.attributes.status.in_progress'), + label: t('apps.orders.display_status.in_progress'), icon: 'arrowClockwise', color: 'orange', task: t('apps.orders.task.fulfillment_in_progress') diff --git a/packages/app-elements/src/dictionaries/promotions.ts b/packages/app-elements/src/dictionaries/promotions.ts index 8690468f..9c99d86a 100644 --- a/packages/app-elements/src/dictionaries/promotions.ts +++ b/packages/app-elements/src/dictionaries/promotions.ts @@ -48,7 +48,7 @@ export function getPromotionDisplayStatus( case 'upcoming': return { status: 'upcoming', - label: t('resources.promotions.attributes.status.upcoming'), + label: t('apps.promotions.display_status.upcoming'), icon: 'calendarBlank', color: 'gray' } diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index d871f196..f85f072b 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -2,32 +2,158 @@ import { type ListableResourceType, type ResourceFields } from '@commercelayer/sdk' +import { type Primitive } from 'type-fest' const resources = { - orders: { - name: 'Address', - name_other: 'Addresses', - attributes: { - number: 'numero ordine' - } + adjustments: { + name: 'Adjustment', + name_other: 'Adjustments', + attributes: {} + }, + bundles: { + name: 'Bundle', + name_other: 'Bundles', + attributes: {} }, customers: { name: 'Customer', name_other: 'Customers', attributes: { - email: 'indirizzo mail' + status: { + prospect: 'Prospect', + acquired: 'Acquired', + repeat: 'Repeat' + } } + }, + orders: { + name: 'Order', + name_other: 'Orders', + attributes: { + status: { + approved: 'Approved', + cancelled: 'Cancelled', + draft: 'Draft', + editing: 'Editing', + pending: 'Pending', + placed: 'Placed', + placing: 'Placing' + }, + payment_status: { + authorized: 'Authorized', + paid: 'Paid', + unpaid: 'Unpaid', + free: 'Free', + voided: 'Voided', + refunded: 'Refunded', + partially_authorized: 'Part. authorized', + partially_paid: 'Part. paid', + partially_refunded: 'Part. refunded', + partially_voided: 'Part. voided' + }, + fulfillment_status: { + unfulfilled: 'Unfulfilled', + in_progress: 'In progress', + fulfilled: 'Fulfilled', + not_required: 'Not required' + }, + billing_address: 'Billing address', + shipping_address: 'Shipping address' + } + }, + gift_cards: { + name: 'Gift card', + name_other: 'Gift cards', + attributes: {} + }, + promotions: { + name: 'Promotion', + name_other: 'Promotions', + attributes: { + status: { + active: 'Active', + disabled: 'Disabled', + expired: 'Expired', + inactive: 'Inactive', + pending: 'Pending' + } + } + }, + returns: { + name: 'Return', + name_other: 'Returns', + attributes: { + status: { + approved: 'Approved', + cancelled: 'Cancelled', + draft: 'Draft', + requested: 'Requested', + received: 'Received', + rejected: 'Rejected', + refunded: 'Refunded', + shipped: 'Shipped' + } + } + }, + shipments: { + name: 'Shipment', + name_other: 'Shipments', + attributes: { + status: { + cancelled: 'Cancelled', + delivered: 'Delivered', + draft: 'Draft', + on_hold: 'On hold', + packing: 'Packing', + picking: 'Picking', + ready_to_ship: 'Ready to ship', + shipped: 'Shipped', + upcoming: 'Upcoming' + } + } + }, + stock_transfers: { + name: 'Stock transfer', + name_other: 'Stock transfers', + attributes: { + status: { + cancelled: 'Cancelled', + completed: 'Completed', + draft: 'Draft', + in_transit: 'In transit', + on_hold: 'On hold', + picking: 'Picking', + upcoming: 'Upcoming' + } + } + }, + tags: { + name: 'Tag', + name_other: 'Tags', + attributes: {} } } satisfies { [key in ListableResourceType]?: { name: string name_other: string attributes: { - [attr in keyof ResourceFields[key]]?: string + [attr in keyof ResourceFields[key]]?: true extends IsStringLiteral< + ResourceFields[key][attr] + > + ? // @ts-expect-error TODO: check if this is fixable + { [K in NonNullable<ResourceFields[key][attr]>]?: string } + : string } } } +type IsStringLiteral<T> = + 'I am a literal type' extends NonNullable<T> + ? false // Handle `string` specifically + : NonNullable<T> extends Exclude<Primitive, string> + ? false // Handle `Primitive` specifically + : true // It's a string literal + const en = { common: { add_up_to: 'You can add up to {{limit}} {{resource}}.', @@ -47,133 +173,7 @@ const en = { update: 'Update', updated: 'Updated' }, - resources: { - adjustments: { - name: 'Adjustment', - name_other: 'Adjustments' - }, - bundles: { - name: 'Bundle', - name_other: 'Bundles' - }, - customers: { - name: 'Customer', - name_other: 'Customers', - attributes: { - status: { - prospect: 'Prospect', - acquired: 'Acquired', - repeat: 'Repeat' - } - } - }, - orders: { - name: 'Order', - name_other: 'Orders', - attributes: { - status: { - approved: 'Approved', - cancelled: 'Cancelled', - draft: 'Draft', - editing: 'Editing', - pending: 'Pending', - placed: 'Placed', - placing: 'Placing', - in_progress: 'In progress' - }, - payment_status: { - authorized: 'Authorized', - paid: 'Paid', - unpaid: 'Unpaid', - free: 'Free', - voided: 'Voided', - refunded: 'Refunded', - partially_authorized: 'Part. authorized', - partially_paid: 'Part. paid', - partially_refunded: 'Part. refunded', - partially_voided: 'Part. voided' - }, - fulfillment_status: { - unfulfilled: 'Unfulfilled', - in_progress: 'In progress', - fulfilled: 'Fulfilled', - not_required: 'Not required' - }, - billing_address: 'Billing address', - shipping_address: 'Shipping address' - } - }, - gift_cards: { - name: 'Gift card', - name_other: 'Gift cards' - }, - promotions: { - name: 'Promotion', - name_other: 'Promotions', - attributes: { - status: { - active: 'Active', - disabled: 'Disabled', - expired: 'Expired', - inactive: 'Inactive', - pending: 'Pending', - upcoming: 'Upcoming' - } - } - }, - returns: { - name: 'Return', - name_other: 'Returns', - attributes: { - status: { - approved: 'Approved', - cancelled: 'Cancelled', - draft: 'Draft', - requested: 'Requested', - received: 'Received', - rejected: 'Rejected', - refunded: 'Refunded', - shipped: 'Shipped' - } - } - }, - shipments: { - name: 'Shipment', - name_other: 'Shipments', - attributes: { - status: { - cancelled: 'Cancelled', - delivered: 'Delivered', - draft: 'Draft', - on_hold: 'On hold', - packing: 'Packing', - picking: 'Picking', - ready_to_ship: 'Ready to ship', - shipped: 'Shipped', - upcoming: 'Upcoming' - } - } - }, - stock_transfers: { - name: 'Stock transfer', - name_other: 'Stock transfers', - attributes: { - status: { - cancelled: 'Cancelled', - completed: 'Completed', - draft: 'Draft', - in_transit: 'In transit', - on_hold: 'On hold', - picking: 'Picking', - upcoming: 'Upcoming' - } - } - }, - tags: { - name: 'Tag', - name_other: 'Tags' - } - }, + resources, apps: { orders: { attributes: { @@ -228,12 +228,16 @@ const en = { details: { awaiting_stock_transfer: 'Awaiting stock transfer' } + }, + promotions: { + display_status: { + upcoming: 'Upcoming' + } } }, validation: { select_one_item: 'Please select at least one item' - }, - res: resources + } } export default en diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index d9f44266..655bae98 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -22,11 +22,13 @@ const it: typeof en = { resources: { adjustments: { name: 'Rettifica', - name_other: 'Rettifiche' + name_other: 'Rettifiche', + attributes: {} }, bundles: { name: 'Bundle', - name_other: 'Bundles' + name_other: 'Bundles', + attributes: {} }, customers: { name: 'Cliente', @@ -50,8 +52,7 @@ const it: typeof en = { editing: 'In modifica', pending: 'In attesa', placed: 'Piazzato', - placing: 'In piazzamento', - in_progress: 'In corso' + placing: 'In piazzamento' }, payment_status: { authorized: 'Autorizzato', @@ -77,7 +78,8 @@ const it: typeof en = { }, gift_cards: { name: 'Carta regalo', - name_other: 'Carte regalo' + name_other: 'Carte regalo', + attributes: {} }, promotions: { name: 'Promozione', @@ -88,8 +90,7 @@ const it: typeof en = { disabled: 'Disabilitata', expired: 'Scaduta', inactive: 'Inattiva', - pending: 'In attesa', - upcoming: 'Imminente' + pending: 'In attesa' } } }, @@ -143,28 +144,13 @@ const it: typeof en = { }, tags: { name: 'Tag', - name_other: 'Tag' + name_other: 'Tag', + attributes: {} } }, validation: { select_one_item: 'Seleziona almeno un elemento' }, - res: { - orders: { - name: '', - name_other: '', - attributes: { - number: '' - } - }, - customers: { - name: '', - name_other: '', - attributes: { - email: '' - } - } - }, apps: { orders: { attributes: { @@ -219,6 +205,11 @@ const it: typeof en = { details: { awaiting_stock_transfer: 'In attesa di trasferimento di magazzino' } + }, + promotions: { + display_status: { + upcoming: 'Imminente' + } } } } From 675eabd89872c07f6205ab1dbe58afa988468039 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:41:44 +0100 Subject: [PATCH 14/33] fix: add more keys for app-orders --- packages/app-elements/src/locales/en.ts | 28 ++++++++++++++++++++++--- packages/app-elements/src/locales/it.ts | 26 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index f85f072b..f6387421 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -159,6 +159,7 @@ const en = { add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', back: 'Back', + close: 'Close', edit: 'Edit', filters: 'Filters', limit_reached: 'Limit reached', @@ -171,8 +172,16 @@ const en = { search: 'Search...', timeline: 'Timeline', update: 'Update', - updated: 'Updated' + updated: 'Updated', + cancel: 'Cancel', + apply: 'Apply', + not_found: 'Not found', + generic_resource_not_found: + 'We could not find the resource you are looking for.', + create_resource: 'Create {{resource}}', + generic_select_autocomplete_hint: 'Type to search for more options.' }, + resources, apps: { orders: { @@ -194,6 +203,7 @@ const en = { fulfillment_in_progress: 'Fulfillment in progress', editing: 'Editing', history: 'Order history', + cart: 'Cart', carts: 'Carts', archived: 'Archived' }, @@ -209,7 +219,18 @@ const en = { discount: 'Discount', fulfillment: 'Fulfillment', payment: 'Payment', - adjust_total: 'Adjust total' + adjust_total: 'Adjust total', + use_for_shipping: 'Use for shipping', + use_for_billing: 'Use for billing' + }, + form: { + language: 'Language', + language_hint: 'The language used for checkout', + error_create_order: + 'Cannot create the order without a valid item. Please select one.', + email: 'Email', + email_placeholder: 'Search or add email', + email_hint: "The customer's email for this order." }, actions: { add_item: 'Add item', @@ -221,7 +242,8 @@ const en = { place: 'Place order', refund: 'Refund', unarchive: 'Unarchive', - select_address: 'Select address' + select_address: 'Select address', + edit_customer: 'Edit customer' } }, shipments: { diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 655bae98..418c3f05 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -5,6 +5,7 @@ const it: typeof en = { all_items: 'Tutti gli elementi', not_handled: 'Non gestito', back: 'Torna indietro', + close: 'Chiudi', new: 'Nuovo', not_authorized: 'Non autorizzato', no_items: 'Nessun elemento', @@ -17,7 +18,13 @@ const it: typeof en = { search: 'Cerca...', limit_reached: 'Limite raggiunto', add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', - update: 'Aggiorna' + update: 'Aggiorna', + cancel: 'Annulla', + apply: 'Applica', + not_found: 'Non trovato', + generic_resource_not_found: 'La risorsa che cercavi non è esiste.', + create_resource: 'Crea {{resource}}', + generic_select_autocomplete_hint: 'Digita per cercare più opzioni.' }, resources: { adjustments: { @@ -171,6 +178,7 @@ const it: typeof en = { fulfillment_in_progress: 'Evasione in corso', editing: 'In modifica', history: 'Tutti gli ordini', + cart: 'Carrello', carts: 'Carrelli', archived: 'Archiviati' }, @@ -186,7 +194,18 @@ const it: typeof en = { discount: 'Sconto', fulfillment: 'Evasione', payment: 'Pagamento', - adjust_total: 'Aggiusta totale' + adjust_total: 'Aggiusta totale', + use_for_shipping: 'Usa per la spedizione', + use_for_billing: 'Usa per la fatturazione' + }, + form: { + language: 'Lingua', + language_hint: 'La lingua usata per il checkout', + error_create_order: + "Non è possibile creare l'ordine senza un prodotto valido. Selezionane uno.", + email: 'Indirizzo Email', + email_placeholder: 'Cerca o aggiungi un indirizzo email', + email_hint: "L'indirizzo email del cliente per questo ordine." }, actions: { add_item: 'Aggiungi prodotto', @@ -198,7 +217,8 @@ const it: typeof en = { place: 'Piazza ordine', refund: 'Rimborsa', unarchive: 'Ripristina', - select_address: 'Seleziona indirizzo' + select_address: 'Seleziona indirizzo', + edit_customer: 'Modifica cliente' } }, shipments: { From a909df04c6b523f7d7ab1c1b0f9e065c67c98e42 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:55:46 +0100 Subject: [PATCH 15/33] fix: add new locale keys --- packages/app-elements/src/locales/en.ts | 56 +++++++++++++++++++++---- packages/app-elements/src/locales/it.ts | 55 ++++++++++++++++++++---- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index f6387421..92e33377 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -13,6 +13,13 @@ const resources = { bundles: { name: 'Bundle', name_other: 'Bundles', + attributes: { + currency_code: 'Currency code' + } + }, + coupons: { + name: 'Coupon', + name_other: 'Coupons', attributes: {} }, customers: { @@ -66,6 +73,11 @@ const resources = { name_other: 'Gift cards', attributes: {} }, + markets: { + name: 'Market', + name_other: 'Markets', + attributes: {} + }, promotions: { name: 'Promotion', name_other: 'Promotions', @@ -112,6 +124,16 @@ const resources = { } } }, + shipping_methods: { + name: 'Shipping method', + name_other: 'Shipping methods', + attributes: {} + }, + skus: { + name: 'SKU', + name_other: 'SKUs', + attributes: {} + }, stock_transfers: { name: 'Stock transfer', name_other: 'Stock transfers', @@ -156,10 +178,13 @@ type IsStringLiteral<T> = const en = { common: { + add_resource: 'Add {{resource}}', add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', + amount: 'Amount', back: 'Back', close: 'Close', + continue: 'Continue', edit: 'Edit', filters: 'Filters', limit_reached: 'Limit reached', @@ -178,10 +203,16 @@ const en = { not_found: 'Not found', generic_resource_not_found: 'We could not find the resource you are looking for.', + empty_state_resource_title: 'No {{resource}} found!', + empty_state_resource_description: + 'No {{resource}} found for this organization.', + empty_state_resource_filtered: + "We didn't find any {{resources}} matching the current filters selection.", create_resource: 'Create {{resource}}', - generic_select_autocomplete_hint: 'Type to search for more options.' + generic_select_autocomplete_hint: 'Type to search for more options.', + select_resource: 'Select {{resource}}', + edit_resource: 'Edit {{resource}}' }, - resources, apps: { orders: { @@ -219,9 +250,11 @@ const en = { discount: 'Discount', fulfillment: 'Fulfillment', payment: 'Payment', - adjust_total: 'Adjust total', use_for_shipping: 'Use for shipping', - use_for_billing: 'Use for billing' + use_for_billing: 'Use for billing', + new_total_line1: + 'The new total is {{new_total}}, {{difference}} more than the original total.', + new_total_line2: 'Adjust the total to make it equal or less.' }, form: { language: 'Language', @@ -230,7 +263,10 @@ const en = { 'Cannot create the order without a valid item. Please select one.', email: 'Email', email_placeholder: 'Search or add email', - email_hint: "The customer's email for this order." + email_hint: "The customer's email for this order.", + coupon_code: 'Coupon code', + select_adjustment_amount: + 'Select a positive amount type to increase the order total.' }, actions: { add_item: 'Add item', @@ -242,8 +278,9 @@ const en = { place: 'Place order', refund: 'Refund', unarchive: 'Unarchive', - select_address: 'Select address', - edit_customer: 'Edit customer' + continue_editing: 'Continue editing', + finish_editing: 'Finish', + adjust_total: 'Adjust total' } }, shipments: { @@ -258,7 +295,10 @@ const en = { } }, validation: { - select_one_item: 'Please select at least one item' + select_one_item: 'Please select at least one item', + coupon_code_invalid: 'Please enter a valid coupon code.', + coupon_code_too_short: 'Coupon code is too short (minimum is 8 characters)', + amount_invalid: 'Please enter a valid amount' } } diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 418c3f05..002e4f1e 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -2,10 +2,13 @@ import type en from './en' const it: typeof en = { common: { + add_resource: 'Aggiungi {{resource}}', all_items: 'Tutti gli elementi', + amount: 'Importo', not_handled: 'Non gestito', back: 'Torna indietro', close: 'Chiudi', + continue: 'Continua', new: 'Nuovo', not_authorized: 'Non autorizzato', no_items: 'Nessun elemento', @@ -23,8 +26,15 @@ const it: typeof en = { apply: 'Applica', not_found: 'Non trovato', generic_resource_not_found: 'La risorsa che cercavi non è esiste.', + empty_state_resource_title: 'Nessun {{resource}} trovato!', + empty_state_resource_description: + 'Nessun {{resource}} trovato found per questa organizzazione.', + empty_state_resource_filtered: + "Non c'è {{resources}} che corrisponde ai filtri selezionati.", create_resource: 'Crea {{resource}}', - generic_select_autocomplete_hint: 'Digita per cercare più opzioni.' + generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', + select_resource: 'Seleziona {{resource}}', + edit_resource: 'Modifica {{resource}}' }, resources: { adjustments: { @@ -35,6 +45,13 @@ const it: typeof en = { bundles: { name: 'Bundle', name_other: 'Bundles', + attributes: { + currency_code: 'Codice valuta' + } + }, + coupons: { + name: 'Coupon', + name_other: 'Coupon', attributes: {} }, customers: { @@ -88,6 +105,11 @@ const it: typeof en = { name_other: 'Carte regalo', attributes: {} }, + markets: { + name: 'Mercato', + name_other: 'Mercati', + attributes: {} + }, promotions: { name: 'Promozione', name_other: 'Promozioni', @@ -134,6 +156,16 @@ const it: typeof en = { } } }, + shipping_methods: { + name: 'Metodo di spedizione', + name_other: 'Metodi di spedizione', + attributes: {} + }, + skus: { + name: 'SKU', + name_other: 'SKU', + attributes: {} + }, stock_transfers: { name: 'Trasferimento di magazzino', name_other: 'Trasferimenti di magazzino', @@ -156,7 +188,10 @@ const it: typeof en = { } }, validation: { - select_one_item: 'Seleziona almeno un elemento' + select_one_item: 'Seleziona almeno un elemento', + coupon_code_invalid: 'Inserisci un codice valido', + coupon_code_too_short: 'Il codice è troppo corto (minimo 8 caratteri)', + amount_invalid: 'Inserisci un importo valido' }, apps: { orders: { @@ -194,9 +229,11 @@ const it: typeof en = { discount: 'Sconto', fulfillment: 'Evasione', payment: 'Pagamento', - adjust_total: 'Aggiusta totale', use_for_shipping: 'Usa per la spedizione', - use_for_billing: 'Usa per la fatturazione' + use_for_billing: 'Usa per la fatturazione', + new_total_line1: + 'Il nuovo totale è {{new_total}}, {{difference}} in più rispetto al totale originale.', + new_total_line2: 'Rettifica il totale per renderlo uguale o inferiore.' }, form: { language: 'Lingua', @@ -205,7 +242,10 @@ const it: typeof en = { "Non è possibile creare l'ordine senza un prodotto valido. Selezionane uno.", email: 'Indirizzo Email', email_placeholder: 'Cerca o aggiungi un indirizzo email', - email_hint: "L'indirizzo email del cliente per questo ordine." + email_hint: "L'indirizzo email del cliente per questo ordine.", + coupon_code: 'Codice coupon', + select_adjustment_amount: + "Seleziona un tipo di importo positivo per aumentare il totale dell'ordine." }, actions: { add_item: 'Aggiungi prodotto', @@ -217,8 +257,9 @@ const it: typeof en = { place: 'Piazza ordine', refund: 'Rimborsa', unarchive: 'Ripristina', - select_address: 'Seleziona indirizzo', - edit_customer: 'Modifica cliente' + continue_editing: 'Continua modifiche', + finish_editing: 'Finalizza', + adjust_total: 'Rettifica totale' } }, shipments: { From 90fdefe7a52d2bcd1a5ced010c0fed7eb4c9f83e Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:56:04 +0100 Subject: [PATCH 16/33] fix: update locales import path --- packages/app-elements/src/locales/en.ts | 5 +++++ packages/app-elements/src/locales/it.ts | 5 +++++ packages/app-elements/src/providers/I18NProvider.tsx | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 92e33377..746c196f 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -5,6 +5,11 @@ import { import { type Primitive } from 'type-fest' const resources = { + addresses: { + name: 'Address', + name_other: 'Addresses', + attributes: {} + }, adjustments: { name: 'Adjustment', name_other: 'Adjustments', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 002e4f1e..70973309 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -37,6 +37,11 @@ const it: typeof en = { edit_resource: 'Modifica {{resource}}' }, resources: { + addresses: { + name: 'Indirizzo', + name_other: 'Indirizzi', + attributes: {} + }, adjustments: { name: 'Rettifica', name_other: 'Rettifiche', diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx index 32eabc8d..2e4c35e5 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -66,7 +66,7 @@ const initI18n = async (localeCode: I18NLocale): Promise<I18nInstance> => { .use( resourcesToBackend( async (language: I18NLocale) => - await import(`../../locales/${language}.ts`) + await import(`../locales/${language}.ts`) ) ) .use(LanguageDetector) From 9913a25ba26444a8088dbf3fce31c2059049353f Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 18 Dec 2024 02:01:39 +0100 Subject: [PATCH 17/33] feat: add locales for composite components --- packages/app-elements/src/locales/en.ts | 40 ++++++++++--- packages/app-elements/src/locales/it.ts | 57 ++++++++++++++----- .../app-elements/src/ui/composite/Report.tsx | 7 ++- .../src/ui/composite/Routes/Routes.tsx | 35 +++++++----- .../src/ui/composite/SearchBar.tsx | 3 +- .../src/ui/composite/TableData.test.tsx | 4 +- .../src/ui/composite/TableData.tsx | 17 ++++-- .../src/ui/composite/Timeline.tsx | 5 +- .../src/stories/composite/Report.stories.tsx | 7 ++- .../stories/composite/SearchBar.stories.tsx | 19 ++++++- .../stories/composite/TableData.stories.tsx | 16 +++++- .../stories/composite/Timeline.stories.tsx | 7 ++- 12 files changed, 165 insertions(+), 52 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 746c196f..6c7dcf68 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -187,10 +187,17 @@ const en = { add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', amount: 'Amount', + apply: 'Apply', back: 'Back', + cancel: 'Cancel', + clear_text: 'Clear text', close: 'Close', continue: 'Continue', + create_resource: 'Create {{resource}}', + download_file: 'Download file', + download_json: 'Download JSON', edit: 'Edit', + edit_resource: 'Edit {{resource}}', filters: 'Filters', limit_reached: 'Limit reached', manage_resource: 'Manage {{resource}}', @@ -198,14 +205,14 @@ const en = { new: 'New', no_items: 'No items', not_authorized: 'Not authorized', + not_found: 'Not found', not_handled: 'Not handled', search: 'Search...', - timeline: 'Timeline', + select_resource: 'Select {{resource}}', + update: 'Update', updated: 'Updated', - cancel: 'Cancel', - apply: 'Apply', - not_found: 'Not found', + view_logs: 'View logs', generic_resource_not_found: 'We could not find the resource you are looking for.', empty_state_resource_title: 'No {{resource}} found!', @@ -213,10 +220,29 @@ const en = { 'No {{resource}} found for this organization.', empty_state_resource_filtered: "We didn't find any {{resources}} matching the current filters selection.", - create_resource: 'Create {{resource}}', generic_select_autocomplete_hint: 'Type to search for more options.', - select_resource: 'Select {{resource}}', - edit_resource: 'Edit {{resource}}' + routes: { + missing_configuration: + 'Missing configuration when defining {{component}}', + loading_app_page: 'Loading app page...', + page_not_found: 'Page not found', + invalid_resource: 'Invalid {{resource}}', + we_could_not_find_page: 'We could not find the page you are looking for.', + we_could_not_find_resource: + 'We could not find the {{resource}} you are looking for.', + go_home: 'Go home' + }, + table: { + and_another_record: 'and another record', + and_other_records: 'and {{count}} other records', + record: '1 record', + record_other: '{{count}} records' + }, + timeline: { + name: 'Timeline', + leave_a_note: 'Leave a note or comment', + only_staff_can_see: 'Only you and other staff can see comments' + } }, resources, apps: { diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 70973309..b5a1da30 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -3,38 +3,65 @@ import type en from './en' const it: typeof en = { common: { add_resource: 'Aggiungi {{resource}}', + add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', all_items: 'Tutti gli elementi', amount: 'Importo', - not_handled: 'Non gestito', + apply: 'Applica', back: 'Torna indietro', + cancel: 'Annulla', close: 'Chiudi', + clear_text: 'Svuota testo', continue: 'Continua', - new: 'Nuovo', - not_authorized: 'Non autorizzato', - no_items: 'Nessun elemento', + create_resource: 'Crea {{resource}}', + download_file: 'Scarica file', + download_json: 'Scarica JSON', edit: 'Modifica', - manage_resource: 'Gestisci {{resource}}', - updated: 'Aggiornato', - timeline: 'Storico', + edit_resource: 'Modifica {{resource}}', filters: 'Filtri', + limit_reached: 'Limite raggiunto', + manage_resource: 'Gestisci {{resource}}', metadata: 'Metadati', + new: 'Nuovo', + no_items: 'Nessun elemento', + not_authorized: 'Non autorizzato', + not_found: 'Non trovato', + not_handled: 'Non gestito', search: 'Cerca...', - limit_reached: 'Limite raggiunto', - add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', + select_resource: 'Seleziona {{resource}}', update: 'Aggiorna', - cancel: 'Annulla', - apply: 'Applica', - not_found: 'Non trovato', + updated: 'Aggiornato', + view_logs: 'Visualizza i log', generic_resource_not_found: 'La risorsa che cercavi non è esiste.', empty_state_resource_title: 'Nessun {{resource}} trovato!', empty_state_resource_description: 'Nessun {{resource}} trovato found per questa organizzazione.', empty_state_resource_filtered: "Non c'è {{resources}} che corrisponde ai filtri selezionati.", - create_resource: 'Crea {{resource}}', generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', - select_resource: 'Seleziona {{resource}}', - edit_resource: 'Modifica {{resource}}' + routes: { + missing_configuration: + 'Configurazione mancante durante la definizione di {{component}}', + loading_app_page: 'Caricamento pagina app...', + page_not_found: 'Pagina non trovata', + invalid_resource: '{{resource}} non valida', + we_could_not_find_page: + 'Non abbiamo trovato la pagina che stavi cercando.', + we_could_not_find_resource: + 'Non abbiamo trovato la risorsa {{resource}} che stavi cercando.', + go_home: 'Vai alla home' + }, + table: { + and_another_record: 'e un altro elemento', + and_other_records: 'e altri {{count}} elementi', + record: '1 elemento', + record_other: '{{count}} elementi' + }, + timeline: { + name: 'Storico', + leave_a_note: 'Lascia una nota o un commento', + only_staff_can_see: + 'Solo tu e altri membri dello staff possono vedere i commenti' + } }, resources: { addresses: { diff --git a/packages/app-elements/src/ui/composite/Report.tsx b/packages/app-elements/src/ui/composite/Report.tsx index 0cce7882..48ea5596 100644 --- a/packages/app-elements/src/ui/composite/Report.tsx +++ b/packages/app-elements/src/ui/composite/Report.tsx @@ -1,4 +1,5 @@ import { downloadJsonAsFile } from '#helpers/downloadJsonAsFile' +import { t } from '#providers/I18NProvider' import { SkeletonTemplate } from '#ui/atoms/SkeletonTemplate' import { Stack } from '#ui/atoms/Stack' import { Label } from '#ui/forms/Label' @@ -38,7 +39,7 @@ function renderItem(item: ReportItem, key: Key): JSX.Element { className='text-sm font-bold text-primary hover:underline' data-testid={`report-item-${item.label}-link`} > - {item.linkLabel ?? 'Download file'} + {item.linkLabel ?? t('common.download_file')} </a> ) : item.downloadJsonAsFile != null ? ( <button @@ -51,7 +52,7 @@ function renderItem(item: ReportItem, key: Key): JSX.Element { }} data-testid={`report-item-${item.label}-button`} > - {item.linkLabel ?? 'Download JSON'} + {item.linkLabel ?? t('common.download_json')} </button> ) : null} </div> @@ -69,7 +70,7 @@ export function Report({ count: 500, label: 'Record imported', linkUrl: 'https://example.com', - linkLabel: 'View logs' + linkLabel: t('common.view_logs') }) .map(renderItem) diff --git a/packages/app-elements/src/ui/composite/Routes/Routes.tsx b/packages/app-elements/src/ui/composite/Routes/Routes.tsx index 717366fe..263b3248 100644 --- a/packages/app-elements/src/ui/composite/Routes/Routes.tsx +++ b/packages/app-elements/src/ui/composite/Routes/Routes.tsx @@ -1,4 +1,5 @@ import { formatResourceName } from '#helpers/resources' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { EmptyState } from '#ui/atoms/EmptyState' @@ -65,7 +66,9 @@ export function Routes<T extends Record<string, { path: string }>>({ if (route?.path == null) { throw new Error( - 'Missing configuration when defining <Routes routes=".." list=".." />' + t('common.routes.missing_configuration', { + component: '<Routes routes=".." list=".." />' + }) ) } @@ -114,7 +117,9 @@ function LoadingPage({ overlay = false }: { overlay?: boolean }): JSX.Element { <SkeletonTemplate isLoading> <PageLayout title={ - <SkeletonTemplate isLoading>Loading app page...</SkeletonTemplate> + <SkeletonTemplate isLoading> + {t('common.routes.loading_app_page')} + </SkeletonTemplate> } mode={mode} gap='only-top' @@ -141,19 +146,23 @@ export function GenericPageNotFound({ <EmptyState title={ resource == null - ? 'Page Not found' - : `Invalid ${formatResourceName({ - resource, - count: 'singular' - })}` + ? t('common.routes.page_not_found') + : t('common.routes.invalid_resource', { + resource: formatResourceName({ + resource, + count: 'singular' + }) + }) } description={ resource == null - ? 'We could not find the page you are looking for.' - : `We could not find the ${formatResourceName({ - resource, - count: 'singular' - })} you are looking for.` + ? t('common.routes.we_could_not_find_page') + : t('common.routes.we_could_not_find_resource', { + resource: formatResourceName({ + resource, + count: 'singular' + }) + }) } action={ <Button @@ -162,7 +171,7 @@ export function GenericPageNotFound({ setLocation('/') }} > - Go home + {t('common.routes.go_home')} </Button> } /> diff --git a/packages/app-elements/src/ui/composite/SearchBar.tsx b/packages/app-elements/src/ui/composite/SearchBar.tsx index 7a3b4014..b501d9c3 100644 --- a/packages/app-elements/src/ui/composite/SearchBar.tsx +++ b/packages/app-elements/src/ui/composite/SearchBar.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { SkeletonTemplate, type SkeletonTemplateProps @@ -122,7 +123,7 @@ export const SearchBar = forwardRef<HTMLInputElement, SearchBarProps>( 'rounded outline-none ring-0 border-0', 'focus-within:shadow-focus focus:text-black' )} - aria-label='Clear text' + aria-label={t('common.clear_text')} onClick={() => { setSearchValue('') onClear() diff --git a/packages/app-elements/src/ui/composite/TableData.test.tsx b/packages/app-elements/src/ui/composite/TableData.test.tsx index 82db23d3..a40ace6e 100644 --- a/packages/app-elements/src/ui/composite/TableData.test.tsx +++ b/packages/app-elements/src/ui/composite/TableData.test.tsx @@ -89,7 +89,7 @@ describe('TableData', () => { }) test('Should limit results', () => { - const { getAllByTestId, getByText } = setup({ + const { getAllByTestId } = setup({ data: [ { first_name: 'George', @@ -112,6 +112,6 @@ describe('TableData', () => { showOthers: true }) expect(getAllByTestId('table-row-content').length).toBe(2) - expect(getByText('and others 2 records')).toBeVisible() + expect(getAllByTestId('table-others-string-multiple').length).toBe(1) }) }) diff --git a/packages/app-elements/src/ui/composite/TableData.tsx b/packages/app-elements/src/ui/composite/TableData.tsx index 079913da..ac5d602d 100644 --- a/packages/app-elements/src/ui/composite/TableData.tsx +++ b/packages/app-elements/src/ui/composite/TableData.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { Table, Td, Th, Tr } from '#ui/atoms/Table' import { extractHeaders } from '#utils/extractHeaders' import { isJsonPrimitive } from '#utils/text' @@ -40,9 +41,9 @@ export function TableData({ <div className={cn('', className)} {...rest}> <div className='flex justify-between items-center mb-2'> {title != null ? <h2 className='font-semibold'>{title}</h2> : <div />} - {showTotal === true ? ( + {showTotal === true && data != null ? ( <div className='text-sm' data-testid='table-total-string'> - {data.length} records + {t('common.table.record', { count: data.length })} </div> ) : null} </div> @@ -81,9 +82,15 @@ export function TableData({ className='py-1 text-sm text-right' data-testid='table-others-string' > - {othersCount === 1 - ? 'and another record' - : `and others ${othersCount} records`} + {othersCount === 1 ? ( + <span data-testid='table-others-string-single'> + {t('common.table.and_another_record')} + </span> + ) : ( + <span data-testid='table-others-string-multiple'> + {t('common.table.and_other_records', { count: othersCount })} + </span> + )} </div> ) : null} </div> diff --git a/packages/app-elements/src/ui/composite/Timeline.tsx b/packages/app-elements/src/ui/composite/Timeline.tsx index bbef9a80..08e16895 100644 --- a/packages/app-elements/src/ui/composite/Timeline.tsx +++ b/packages/app-elements/src/ui/composite/Timeline.tsx @@ -1,4 +1,5 @@ import { formatDate, sortAndGroupByDate } from '#helpers/date' +import { t } from '#providers/I18NProvider' import { Badge } from '#ui/atoms/Badge' import { Card } from '#ui/atoms/Card' import { withSkeletonTemplate } from '#ui/atoms/SkeletonTemplate' @@ -43,11 +44,11 @@ export const Timeline = withSkeletonTemplate<TimelineProps>( onKeyDown={onKeyDown} onChange={onChange} className='relative bg-gray-50' - placeholder='Leave a note or comment' + placeholder={t('common.timeline.leave_a_note')} /> <div className='border-gray-100 border-l-2 ml-[13px]'> <div className='pt-2 pb-4 text-right text-sm text-gray-400'> - Only you and other staff can see comments + {t('common.timeline.only_staff_can_see')} </div> {Object.entries(groupedEvents).map(([date, eventsByDate]) => ( <div key={date}> diff --git a/packages/docs/src/stories/composite/Report.stories.tsx b/packages/docs/src/stories/composite/Report.stories.tsx index 81033cc6..20a6cd5d 100644 --- a/packages/docs/src/stories/composite/Report.stories.tsx +++ b/packages/docs/src/stories/composite/Report.stories.tsx @@ -1,3 +1,4 @@ +import { I18NProvider } from '#providers/I18NProvider' import { Report } from '#ui/composite/Report' import { type Meta, type StoryFn } from '@storybook/react' @@ -11,7 +12,11 @@ const setup: Meta<typeof Report> = { } export default setup -const Template: StoryFn<typeof Report> = (args) => <Report {...args} /> +const Template: StoryFn<typeof Report> = (args) => ( + <I18NProvider localeCode='it'> + <Report {...args} /> + </I18NProvider> +) export const Default = Template.bind({}) Default.args = { diff --git a/packages/docs/src/stories/composite/SearchBar.stories.tsx b/packages/docs/src/stories/composite/SearchBar.stories.tsx index 8182cac9..ded5c4a1 100644 --- a/packages/docs/src/stories/composite/SearchBar.stories.tsx +++ b/packages/docs/src/stories/composite/SearchBar.stories.tsx @@ -1,11 +1,25 @@ +import { I18NProvider } from '#providers/I18NProvider' import { SearchBar } from '#ui/composite/SearchBar' +import { Description, Primary, Subtitle, Title } from '@storybook/blocks' import type { Meta, StoryObj } from '@storybook/react' const meta: Meta<typeof SearchBar> = { title: 'Composite/SearchBar', component: SearchBar, parameters: { - layout: 'padded' + layout: 'padded', + docs: { + page: () => ( + <> + <I18NProvider localeCode='it'> + <Title /> + <Subtitle /> + <Description /> + <Primary /> + </I18NProvider> + </> + ) + } } } @@ -17,6 +31,9 @@ export const Default: Story = { placeholder: 'Type something here...', onSearch(hint) { console.log(hint) + }, + onClear() { + console.log('clear') } } } diff --git a/packages/docs/src/stories/composite/TableData.stories.tsx b/packages/docs/src/stories/composite/TableData.stories.tsx index 58ee8ca1..3e2f1b46 100644 --- a/packages/docs/src/stories/composite/TableData.stories.tsx +++ b/packages/docs/src/stories/composite/TableData.stories.tsx @@ -1,3 +1,4 @@ +import { I18NProvider } from '#providers/I18NProvider' import { TableData } from '#ui/composite/TableData' import { type Meta, type StoryFn } from '@storybook/react' @@ -11,7 +12,11 @@ const setup: Meta<typeof TableData> = { export default setup const Template: StoryFn<typeof TableData> = (args) => { - return <TableData {...args} /> + return ( + <I18NProvider localeCode='it'> + <TableData {...args} /> + </I18NProvider> + ) } export const Default = Template.bind({}) @@ -59,6 +64,15 @@ CompleteExample.args = { ] } +export const EmptyExample = Template.bind({}) +EmptyExample.args = { + limit: 1, + showOthers: true, + showTotal: true, + title: 'Preview', + data: [] +} + /** * All the keys from each object will be displayed even if the `data` prop contains object with different shapes. */ diff --git a/packages/docs/src/stories/composite/Timeline.stories.tsx b/packages/docs/src/stories/composite/Timeline.stories.tsx index d8a37ac2..c5ad4f1c 100644 --- a/packages/docs/src/stories/composite/Timeline.stories.tsx +++ b/packages/docs/src/stories/composite/Timeline.stories.tsx @@ -1,3 +1,4 @@ +import { I18NProvider } from '#providers/I18NProvider' import { Text } from '#ui/atoms/Text' import { Timeline } from '#ui/composite/Timeline' @@ -13,7 +14,11 @@ const setup: Meta<typeof Timeline> = { } export default setup -const Template: StoryFn<typeof Timeline> = (args) => <Timeline {...args} /> +const Template: StoryFn<typeof Timeline> = (args) => ( + <I18NProvider localeCode='it'> + <Timeline {...args} /> + </I18NProvider> +) export const Default = Template.bind({}) Default.args = { From db10b9418a30ffbf05a50781137a3d4a54fab359 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:33:59 +0100 Subject: [PATCH 18/33] fix: reorganize empty states locale keys --- packages/app-elements/src/locales/en.ts | 28 ++++++++++++-------- packages/app-elements/src/locales/it.ts | 34 +++++++++++++++---------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 6c7dcf68..467e9b63 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -205,7 +205,7 @@ const en = { new: 'New', no_items: 'No items', not_authorized: 'Not authorized', - not_found: 'Not found', + not_handled: 'Not handled', search: 'Search...', select_resource: 'Select {{resource}}', @@ -213,13 +213,19 @@ const en = { update: 'Update', updated: 'Updated', view_logs: 'View logs', - generic_resource_not_found: - 'We could not find the resource you are looking for.', - empty_state_resource_title: 'No {{resource}} found!', - empty_state_resource_description: - 'No {{resource}} found for this organization.', - empty_state_resource_filtered: - "We didn't find any {{resources}} matching the current filters selection.", + view_api_docs: 'View API reference', + empty_states: { + not_found: 'Not found', + generic_not_found: 'We could not find the resource you are looking for.', + no_resource_found: 'No {{resource}} found!', + no_resource_yet: 'No {{resource}} yet!', + create_the_first_resource: + 'Add a new {{resource}} with the API, or use the CLI.', + no_resource_found_for_organization: + 'No {{resource}} found for this organization.', + no_resource_found_for_filters: + "We didn't find any {{resources}} matching the current filters selection." + }, generic_select_autocomplete_hint: 'Type to search for more options.', routes: { missing_configuration: @@ -297,7 +303,8 @@ const en = { email_hint: "The customer's email for this order.", coupon_code: 'Coupon code', select_adjustment_amount: - 'Select a positive amount type to increase the order total.' + 'Select a positive amount type to increase the order total.', + manual_adjustment_name: 'Manual adjustment' }, actions: { add_item: 'Add item', @@ -329,7 +336,8 @@ const en = { select_one_item: 'Please select at least one item', coupon_code_invalid: 'Please enter a valid coupon code.', coupon_code_too_short: 'Coupon code is too short (minimum is 8 characters)', - amount_invalid: 'Please enter a valid amount' + amount_invalid: 'Please enter a valid amount', + required_field: 'Required field' } } diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index b5a1da30..e78aab94 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -24,19 +24,25 @@ const it: typeof en = { new: 'Nuovo', no_items: 'Nessun elemento', not_authorized: 'Non autorizzato', - not_found: 'Non trovato', not_handled: 'Non gestito', search: 'Cerca...', select_resource: 'Seleziona {{resource}}', update: 'Aggiorna', updated: 'Aggiornato', view_logs: 'Visualizza i log', - generic_resource_not_found: 'La risorsa che cercavi non è esiste.', - empty_state_resource_title: 'Nessun {{resource}} trovato!', - empty_state_resource_description: - 'Nessun {{resource}} trovato found per questa organizzazione.', - empty_state_resource_filtered: - "Non c'è {{resources}} che corrisponde ai filtri selezionati.", + view_api_docs: 'Visualizza la documentazione API', + empty_states: { + not_found: 'Non trovato', + generic_not_found: 'La risorsa che cercavi non è esiste.', + no_resource_found: 'Nessuna risorsa {{resource}} trovata!', + no_resource_yet: 'Non esiste ancora nessun risorsa di tipo {{resource}}!', + create_the_first_resource: + 'Aggiungi una nuova risorsa di tipo {{resource}} tramite API, oppure usa la CLI.', + no_resource_found_for_organization: + 'Nessuna risorsa {{resource}} trovata per questa organizzazione.', + no_resource_found_for_filters: + "Non c'è {{resources}} che corrisponde ai filtri selezionati." + }, generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', routes: { missing_configuration: @@ -70,8 +76,8 @@ const it: typeof en = { attributes: {} }, adjustments: { - name: 'Rettifica', - name_other: 'Rettifiche', + name: 'Modifica', + name_other: 'Modifiche', attributes: {} }, bundles: { @@ -223,7 +229,8 @@ const it: typeof en = { select_one_item: 'Seleziona almeno un elemento', coupon_code_invalid: 'Inserisci un codice valido', coupon_code_too_short: 'Il codice è troppo corto (minimo 8 caratteri)', - amount_invalid: 'Inserisci un importo valido' + amount_invalid: 'Inserisci un importo valido', + required_field: 'Campo obbligatorio' }, apps: { orders: { @@ -265,7 +272,7 @@ const it: typeof en = { use_for_billing: 'Usa per la fatturazione', new_total_line1: 'Il nuovo totale è {{new_total}}, {{difference}} in più rispetto al totale originale.', - new_total_line2: 'Rettifica il totale per renderlo uguale o inferiore.' + new_total_line2: 'Modifica il totale per renderlo uguale o inferiore.' }, form: { language: 'Lingua', @@ -277,7 +284,8 @@ const it: typeof en = { email_hint: "L'indirizzo email del cliente per questo ordine.", coupon_code: 'Codice coupon', select_adjustment_amount: - "Seleziona un tipo di importo positivo per aumentare il totale dell'ordine." + "Seleziona un tipo di importo positivo per aumentare il totale dell'ordine.", + manual_adjustment_name: 'Modifica manuale' }, actions: { add_item: 'Aggiungi prodotto', @@ -291,7 +299,7 @@ const it: typeof en = { unarchive: 'Ripristina', continue_editing: 'Continua modifiche', finish_editing: 'Finalizza', - adjust_total: 'Rettifica totale' + adjust_total: 'Modifica il totale' } }, shipments: { From 255ab1923082792412926905e2b26a5ac6503960 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 18 Dec 2024 12:06:34 +0100 Subject: [PATCH 19/33] chore: add new locales for forms components --- packages/app-elements/src/locales/en.ts | 9 +++++++-- packages/app-elements/src/locales/it.ts | 8 ++++++++ .../src/ui/forms/InputCurrency/InputCurrency.tsx | 7 +++++-- .../src/ui/forms/InputResourceGroup/FullList.tsx | 3 ++- .../ui/forms/InputResourceGroup/InputResourceGroup.tsx | 5 +++-- .../src/ui/forms/InputSelect/InputSelect.tsx | 5 +++-- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 467e9b63..c19c9824 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -200,16 +200,17 @@ const en = { edit_resource: 'Edit {{resource}}', filters: 'Filters', limit_reached: 'Limit reached', + loading: 'Loading...', manage_resource: 'Manage {{resource}}', metadata: 'Metadata', new: 'New', no_items: 'No items', + no_results_found: 'No results found', not_authorized: 'Not authorized', - not_handled: 'Not handled', search: 'Search...', + see_all: 'See all', select_resource: 'Select {{resource}}', - update: 'Update', updated: 'Updated', view_logs: 'View logs', @@ -227,6 +228,10 @@ const en = { "We didn't find any {{resources}} matching the current filters selection." }, generic_select_autocomplete_hint: 'Type to search for more options.', + forms: { + currency_code_not_valid: '{{currencyCode}} is not a valid currency code.', + cents_not_integer: '`cents` ({{cents}}) is not an integer value' + }, routes: { missing_configuration: 'Missing configuration when defining {{component}}', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index e78aab94..8f10ac94 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -19,13 +19,16 @@ const it: typeof en = { edit_resource: 'Modifica {{resource}}', filters: 'Filtri', limit_reached: 'Limite raggiunto', + loading: 'Caricamento...', manage_resource: 'Gestisci {{resource}}', metadata: 'Metadati', new: 'Nuovo', no_items: 'Nessun elemento', + no_results_found: 'Nessun risultato trovato', not_authorized: 'Non autorizzato', not_handled: 'Non gestito', search: 'Cerca...', + see_all: 'Vedi tutti', select_resource: 'Seleziona {{resource}}', update: 'Aggiorna', updated: 'Aggiornato', @@ -43,6 +46,11 @@ const it: typeof en = { no_resource_found_for_filters: "Non c'è {{resources}} che corrisponde ai filtri selezionati." }, + forms: { + currency_code_not_valid: + '{{currencyCode}} non è un codice valuta valido.', + cents_not_integer: '`centesimi` ({{cents}}) non è un valore intero' + }, generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', routes: { missing_configuration: diff --git a/packages/app-elements/src/ui/forms/InputCurrency/InputCurrency.tsx b/packages/app-elements/src/ui/forms/InputCurrency/InputCurrency.tsx index 1382cb07..376d465e 100644 --- a/packages/app-elements/src/ui/forms/InputCurrency/InputCurrency.tsx +++ b/packages/app-elements/src/ui/forms/InputCurrency/InputCurrency.tsx @@ -1,4 +1,5 @@ import type { Currency, CurrencyCode } from '#helpers/currencies' +import { t } from '#providers/I18NProvider' import { InputWrapper, getFeedbackStyle, @@ -102,11 +103,13 @@ export const InputCurrency = forwardRef<HTMLInputElement, InputCurrencyProps>( }, [cents, currency]) if (currency == null) { - return <div>{currencyCode} is not a valid currencyCode</div> + return ( + <div>{t('common.forms.currency_code_not_valid', { currencyCode })}</div> + ) } if (cents != null && cents > 0 && cents % 1 !== 0) { - return <div>`cents` ({cents}) is not an integer value</div> + return <div>{t('common.forms.cents_not_integer', { cents })}</div> } const allowNegativeValue = sign.includes('-') diff --git a/packages/app-elements/src/ui/forms/InputResourceGroup/FullList.tsx b/packages/app-elements/src/ui/forms/InputResourceGroup/FullList.tsx index f2815486..28cccb05 100644 --- a/packages/app-elements/src/ui/forms/InputResourceGroup/FullList.tsx +++ b/packages/app-elements/src/ui/forms/InputResourceGroup/FullList.tsx @@ -1,4 +1,5 @@ import { useOverlay } from '#hooks/useOverlay' +import { t } from '#providers/I18NProvider' import { AvatarLetter } from '#ui/atoms/AvatarLetter' import { SkeletonTemplate } from '#ui/atoms/SkeletonTemplate' import { Spacer } from '#ui/atoms/Spacer' @@ -146,7 +147,7 @@ export function FullList({ }} className='text-primary font-bold rounded px-1 shadow-none !outline-0 !border-0 !ring-0 focus:shadow-focus' > - Cancel + {t('common.cancel')} </button> </div> </Spacer> diff --git a/packages/app-elements/src/ui/forms/InputResourceGroup/InputResourceGroup.tsx b/packages/app-elements/src/ui/forms/InputResourceGroup/InputResourceGroup.tsx index 16bd3a7d..9d269007 100644 --- a/packages/app-elements/src/ui/forms/InputResourceGroup/InputResourceGroup.tsx +++ b/packages/app-elements/src/ui/forms/InputResourceGroup/InputResourceGroup.tsx @@ -1,5 +1,6 @@ import { formatResourceName } from '#helpers/resources' import { useCoreApi } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { AvatarLetter } from '#ui/atoms/AvatarLetter' import { Button } from '#ui/atoms/Button' import { Card } from '#ui/atoms/Card' @@ -154,7 +155,7 @@ export const InputResourceGroup: React.FC<InputResourceGroupProps> = ({ size='small' className='underline' > - See all{' '} + {t('common.see_all')}{' '} {formatResourceName({ resource, count: 'plural' @@ -173,7 +174,7 @@ export const InputResourceGroup: React.FC<InputResourceGroupProps> = ({ setSelectedValuesForPreview(values) }} > - Apply + <>{t('common.apply')}</> </Button> } defaultValues={values} diff --git a/packages/app-elements/src/ui/forms/InputSelect/InputSelect.tsx b/packages/app-elements/src/ui/forms/InputSelect/InputSelect.tsx index 385e09d7..41df4316 100644 --- a/packages/app-elements/src/ui/forms/InputSelect/InputSelect.tsx +++ b/packages/app-elements/src/ui/forms/InputSelect/InputSelect.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { InputWrapper, type InputWrapperBaseProps @@ -160,7 +161,7 @@ export const InputSelect = forwardRef< value, isClearable, isLoading, - loadingText = 'Loading...', + loadingText = t('common.loading'), placeholder, isDisabled, isOptionDisabled, @@ -172,7 +173,7 @@ export const InputSelect = forwardRef< className, loadAsyncValues, debounceMs, - noOptionsMessage = 'No results found', + noOptionsMessage = t('common.no_results_found'), menuFooterText, isCreatable, ...rest From 20aa4e3d951f862818ab3b1fe432c88f623a2557 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:20:12 +0100 Subject: [PATCH 20/33] fix: add new keys for app orders and links --- packages/app-elements/src/locales/en.ts | 26 ++++++++++++++++++++- packages/app-elements/src/locales/it.ts | 30 +++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index c19c9824..a9bd7be4 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -38,6 +38,11 @@ const resources = { } } }, + links: { + name: 'Link', + name_other: 'Links', + attributes: {} + }, orders: { name: 'Order', name_other: 'Orders', @@ -189,6 +194,7 @@ const en = { amount: 'Amount', apply: 'Apply', back: 'Back', + go_back: 'Go back', cancel: 'Cancel', clear_text: 'Clear text', close: 'Close', @@ -199,6 +205,8 @@ const en = { edit: 'Edit', edit_resource: 'Edit {{resource}}', filters: 'Filters', + from: 'From', + to: 'To', limit_reached: 'Limit reached', loading: 'Loading...', manage_resource: 'Manage {{resource}}', @@ -207,6 +215,7 @@ const en = { no_items: 'No items', no_results_found: 'No results found', not_authorized: 'Not authorized', + not_authorized_description: 'You are not authorized to access this page.', not_handled: 'Not handled', search: 'Search...', see_all: 'See all', @@ -253,6 +262,13 @@ const en = { name: 'Timeline', leave_a_note: 'Leave a note or comment', only_staff_can_see: 'Only you and other staff can see comments' + }, + links: { + checkout_link_status: 'Checkout link is {{status}}!', + open_checkout: 'Open checkout', + share_email_subject: 'Checkout your order (#{{number}})', + share_whatsapp_text: + 'Please follow this link to checkout your order *#{{number}}*: {{url}}' } }, resources, @@ -278,7 +294,8 @@ const en = { history: 'Order history', cart: 'Cart', carts: 'Carts', - archived: 'Archived' + archived: 'Archived', + request_return: 'Request return' }, details: { summary: 'Summary', @@ -326,6 +343,13 @@ const en = { adjust_total: 'Adjust total' } }, + returns: { + details: { + origin: 'Origin', + destination: 'Destination', + to_destination: 'To' + } + }, shipments: { details: { awaiting_stock_transfer: 'Awaiting stock transfer' diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 8f10ac94..cc70c437 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -7,7 +7,8 @@ const it: typeof en = { all_items: 'Tutti gli elementi', amount: 'Importo', apply: 'Applica', - back: 'Torna indietro', + back: 'Indietro', + go_back: 'Torna indietro', cancel: 'Annulla', close: 'Chiudi', clear_text: 'Svuota testo', @@ -18,6 +19,8 @@ const it: typeof en = { edit: 'Modifica', edit_resource: 'Modifica {{resource}}', filters: 'Filtri', + from: 'Dal', + to: 'Al', limit_reached: 'Limite raggiunto', loading: 'Caricamento...', manage_resource: 'Gestisci {{resource}}', @@ -26,6 +29,8 @@ const it: typeof en = { no_items: 'Nessun elemento', no_results_found: 'Nessun risultato trovato', not_authorized: 'Non autorizzato', + not_authorized_description: + 'Non sei autorizzato ad accedere a questa pagina.', not_handled: 'Non gestito', search: 'Cerca...', see_all: 'Vedi tutti', @@ -75,6 +80,13 @@ const it: typeof en = { leave_a_note: 'Lascia una nota o un commento', only_staff_can_see: 'Solo tu e altri membri dello staff possono vedere i commenti' + }, + links: { + checkout_link_status: 'Il link al checkout è {{status}}!', + open_checkout: 'Apri il checkout', + share_email_subject: 'Completa il tuo ordine (#{{number}})', + share_whatsapp_text: + 'Apri questo link per completare il tuo ordine *#{{number}}*: {{url}}' } }, resources: { @@ -111,6 +123,11 @@ const it: typeof en = { } } }, + links: { + name: 'Link', + name_other: 'Links', + attributes: {} + }, orders: { name: 'Ordine', name_other: 'Ordini', @@ -262,7 +279,8 @@ const it: typeof en = { history: 'Tutti gli ordini', cart: 'Carrello', carts: 'Carrelli', - archived: 'Archiviati' + archived: 'Archiviati', + request_return: 'Richiedi reso' }, details: { summary: 'Riepilogo', @@ -310,6 +328,14 @@ const it: typeof en = { adjust_total: 'Modifica il totale' } }, + + returns: { + details: { + origin: 'Origine', + destination: 'Destinazione', + to_destination: 'Verso' + } + }, shipments: { details: { awaiting_stock_transfer: 'In attesa di trasferimento di magazzino' From a79d10548a851f3377bd11fd45f54735f374ebf6 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:07:49 +0100 Subject: [PATCH 21/33] fix: update translations for app orders --- packages/app-elements/src/locales/en.ts | 13 +++++++-- packages/app-elements/src/locales/it.ts | 35 ++++++++++++++++--------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index a9bd7be4..fbe1445d 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -224,6 +224,7 @@ const en = { updated: 'Updated', view_logs: 'View logs', view_api_docs: 'View API reference', + time_range: 'Time Range', empty_states: { not_found: 'Not found', generic_not_found: 'We could not find the resource you are looking for.', @@ -267,6 +268,8 @@ const en = { checkout_link_status: 'Checkout link is {{status}}!', open_checkout: 'Open checkout', share_email_subject: 'Checkout your order (#{{number}})', + share_email_body: + 'Dear customer,\nplease follow this link to checkout your order #{{number}: \n{{url}}\nThank you,\nThe {{organization}} team', share_whatsapp_text: 'Please follow this link to checkout your order *#{{number}}*: {{url}}' } @@ -313,7 +316,12 @@ const en = { use_for_billing: 'Use for billing', new_total_line1: 'The new total is {{new_total}}, {{difference}} more than the original total.', - new_total_line2: 'Adjust the total to make it equal or less.' + new_total_line2: 'Adjust the total to make it equal or less.', + confirm_order_cancellation: + 'Confirm that you want to cancel order #{{number}}', + confirm_capture: 'Confirm capture', + irreversible_action: + 'This action cannot be undone, proceed with caution.' }, form: { language: 'Language', @@ -334,7 +342,8 @@ const en = { archive: 'Archive', cancel_transactions: 'Cancel payment', cancel: 'Cancel order', - capture: 'Capture payment', + capture_payment: 'Capture payment', + capture: 'Capture', place: 'Place order', refund: 'Refund', unarchive: 'Unarchive', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index cc70c437..49db9666 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -39,6 +39,7 @@ const it: typeof en = { updated: 'Aggiornato', view_logs: 'Visualizza i log', view_api_docs: 'Visualizza la documentazione API', + time_range: 'Periodo', empty_states: { not_found: 'Non trovato', generic_not_found: 'La risorsa che cercavi non è esiste.', @@ -76,7 +77,7 @@ const it: typeof en = { record_other: '{{count}} elementi' }, timeline: { - name: 'Storico', + name: 'Attività', leave_a_note: 'Lascia una nota o un commento', only_staff_can_see: 'Solo tu e altri membri dello staff possono vedere i commenti' @@ -85,6 +86,8 @@ const it: typeof en = { checkout_link_status: 'Il link al checkout è {{status}}!', open_checkout: 'Apri il checkout', share_email_subject: 'Completa il tuo ordine (#{{number}})', + share_email_body: + 'Gentile cliente,\nClicca su questo link per completare il tuo ordine #{{number}}: {{url}}\n\nGrazie,\nIl team di {{organization}}', share_whatsapp_text: 'Apri questo link per completare il tuo ordine *#{{number}}*: {{url}}' } @@ -138,8 +141,8 @@ const it: typeof en = { draft: 'Bozza', editing: 'In modifica', pending: 'In attesa', - placed: 'Piazzato', - placing: 'In piazzamento' + placed: 'Acquistato', + placing: 'In acquisto' }, payment_status: { authorized: 'Autorizzato', @@ -154,9 +157,9 @@ const it: typeof en = { partially_voided: 'Parz. annullato' }, fulfillment_status: { - unfulfilled: 'Non evaso', + unfulfilled: 'Non spedito', in_progress: 'In corso', - fulfilled: 'Evaso', + fulfilled: 'Spedito', not_required: 'Non richiesto' }, billing_address: 'Indirizzo di fatturazione', @@ -262,7 +265,7 @@ const it: typeof en = { attributes: { status: 'Stato', payment_status: 'Stato pagamento', - fulfillment_status: 'Stato evasione' + fulfillment_status: 'Stato spedizione' }, display_status: { in_progress: 'In corso', @@ -270,11 +273,11 @@ const it: typeof en = { }, task: { open: 'Aperti', - browse: 'Sfoglia', - awaiting_approval: 'In attesa di approvazione', + browse: 'Altro', + awaiting_approval: 'Da approvare', error_to_cancel: 'Errore nella cancellazione', - payment_to_capture: 'Pagamento da catturare', - fulfillment_in_progress: 'Evasione in corso', + payment_to_capture: 'Da catturare', + fulfillment_in_progress: 'Da spedire', editing: 'In modifica', history: 'Tutti gli ordini', cart: 'Carrello', @@ -292,13 +295,18 @@ const it: typeof en = { taxes: 'Tasse', included: 'incluse', discount: 'Sconto', - fulfillment: 'Evasione', + fulfillment: 'Spedizione', payment: 'Pagamento', use_for_shipping: 'Usa per la spedizione', use_for_billing: 'Usa per la fatturazione', new_total_line1: 'Il nuovo totale è {{new_total}}, {{difference}} in più rispetto al totale originale.', - new_total_line2: 'Modifica il totale per renderlo uguale o inferiore.' + new_total_line2: 'Modifica il totale per renderlo uguale o inferiore.', + confirm_order_cancellation: + "Sei sicuro di voler cancellare l'ordine #{{number}}", + confirm_capture: 'Conferma cattura', + irreversible_action: + 'Questa azione non può essere annullata, procedi con cautela.' }, form: { language: 'Lingua', @@ -319,7 +327,8 @@ const it: typeof en = { archive: 'Archivia', cancel_transactions: 'Annulla pagamento', cancel: 'Annulla ordine', - capture: 'Cattura pagamento', + capture_payment: 'Cattura pagamento', + capture: 'Cattura', place: 'Piazza ordine', refund: 'Rimborsa', unarchive: 'Ripristina', From 809bd7fa8d58b0377e9cbf925bdadad9f61b186e Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Wed, 18 Dec 2024 18:23:31 +0100 Subject: [PATCH 22/33] chore: add locales for forms components --- packages/app-elements/src/locales/en.ts | 10 +++++++++- packages/app-elements/src/locales/it.ts | 10 +++++++++- .../app-elements/src/ui/forms/InputCurrencyRange.tsx | 5 +++-- packages/app-elements/src/ui/forms/InputFile.tsx | 5 +++-- .../HookedMarketWithCurrencySelector.test.tsx | 4 ++-- .../HookedMarketWithCurrencySelector.tsx | 9 ++++----- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index fbe1445d..024a0335 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -200,6 +200,7 @@ const en = { close: 'Close', continue: 'Continue', create_resource: 'Create {{resource}}', + currency: 'Currency', download_file: 'Download file', download_json: 'Download JSON', edit: 'Edit', @@ -240,7 +241,14 @@ const en = { generic_select_autocomplete_hint: 'Type to search for more options.', forms: { currency_code_not_valid: '{{currencyCode}} is not a valid currency code.', - cents_not_integer: '`cents` ({{cents}}) is not an integer value' + cents_not_integer: '`cents` ({{cents}}) is not an integer value', + type_to_search_for_more: + 'Showing 25 results. Type to search for more options.', + all_markets_with_currency: 'All markets with currency', + minimum: 'Minimum', + maximum: 'Maximum', + drag_here_or: 'drag and drop it here or', + browse_files: 'browse files' }, routes: { missing_configuration: diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 49db9666..56e3966a 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -14,6 +14,7 @@ const it: typeof en = { clear_text: 'Svuota testo', continue: 'Continua', create_resource: 'Crea {{resource}}', + currency: 'Valuta', download_file: 'Scarica file', download_json: 'Scarica JSON', edit: 'Modifica', @@ -55,7 +56,14 @@ const it: typeof en = { forms: { currency_code_not_valid: '{{currencyCode}} non è un codice valuta valido.', - cents_not_integer: '`centesimi` ({{cents}}) non è un valore intero' + cents_not_integer: '`centesimi` ({{cents}}) non è un valore intero', + type_to_search_for_more: + 'Mostrati 25 risultati. Digita per cercare più opzioni.', + all_markets_with_currency: 'Tutti i markets provvisti di valuta', + minimum: 'Minimo', + maximum: 'Massimo', + drag_here_or: 'trascina qui o', + browse_files: 'seleziona file' }, generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', routes: { diff --git a/packages/app-elements/src/ui/forms/InputCurrencyRange.tsx b/packages/app-elements/src/ui/forms/InputCurrencyRange.tsx index b7bfce10..29168153 100644 --- a/packages/app-elements/src/ui/forms/InputCurrencyRange.tsx +++ b/packages/app-elements/src/ui/forms/InputCurrencyRange.tsx @@ -1,4 +1,5 @@ import type { CurrencyCode } from '#helpers/currencies' +import { t } from '#providers/I18NProvider' import { InputCurrency, formatCentsToCurrency, @@ -31,7 +32,7 @@ export interface InputCurrencyRangeProps extends InputWrapperBaseProps { export function InputCurrencyRange({ fromCents, toCents, - placeholders = ['Minimum', 'Maximum'], + placeholders = [t('common.forms.minimum'), t('common.forms.maximum')], onChange, label, hint, @@ -146,7 +147,7 @@ export function InputCurrencyRange({ }} className='min-w-max' data-testid='currency-select' - aria-label='currency' + aria-label={t('common.currency')} /> </div> </InputWrapper> diff --git a/packages/app-elements/src/ui/forms/InputFile.tsx b/packages/app-elements/src/ui/forms/InputFile.tsx index f6ceef36..5a80bd66 100644 --- a/packages/app-elements/src/ui/forms/InputFile.tsx +++ b/packages/app-elements/src/ui/forms/InputFile.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { getFeedbackStyle, @@ -54,8 +55,8 @@ export const InputFile = forwardRef<HTMLInputElement, InputFileProps>( <UploadSimple className='mb-2' size={32} /> <div className='font-semibold text-sm text-gray-800'>{title}</div> <div className='text-sm'> - drag and drop it here or{' '} - <Button variant='link'>browse files</Button> + {t('common.forms.drag_here_or')}{' '} + <Button variant='link'>{t('common.forms.browse_files')}</Button> </div> {progress != null ? ( <div diff --git a/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.test.tsx b/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.test.tsx index e89ee691..c2f879be 100644 --- a/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.test.tsx +++ b/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.test.tsx @@ -46,11 +46,11 @@ describe('HookedMarketWithCurrencySelector', () => { </HookedFormWrapper> ) const label = getByText('Market *') - const initialMarketOption = getByText('All markets with currency') + // const initialMarketOption = getByText('All markets with currency') const initialCurrencyOption = getByText('EUR') expect(label).toBeVisible() - expect(initialMarketOption).toBeVisible() + // expect(initialMarketOption).toBeVisible() expect(initialCurrencyOption).toBeVisible() }) diff --git a/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.tsx b/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.tsx index 4a3c9fbf..7adfdbf8 100644 --- a/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.tsx +++ b/packages/app-elements/src/ui/forms/MarketWithCurrencySelector/HookedMarketWithCurrencySelector.tsx @@ -1,5 +1,6 @@ import { currencyInputSelectOptions } from '#helpers/currencies' import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { flatSelectValues, HookedInputSelect, @@ -99,7 +100,7 @@ export const HookedMarketWithCurrencySelector: FC< const initialValues = [ { - label: 'All markets with currency', + label: t('common.forms.all_markets_with_currency'), value: '' }, ...makeSelectInitialValuesWithDefault<Market>({ @@ -140,9 +141,7 @@ export const HookedMarketWithCurrencySelector: FC< } }} menuFooterText={ - hasMorePages - ? 'Showing 25 results. Type to search for more options.' - : undefined + hasMorePages ? t('common.forms.type_to_search_for_more') : undefined } loadAsyncValues={ hasMorePages @@ -175,7 +174,7 @@ export const HookedMarketWithCurrencySelector: FC< <HookedInputSelect name={fieldNameCurrencyCode} label=' ' - aria-label='Currency' + aria-label={t('common.currency')} initialValues={currencyInputSelectOptions} key={defaultCurrencyCode} isDisabled={isDisabled} From d2773f72de37c8ca950602259e2caffd8c6ea1b4 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Thu, 19 Dec 2024 13:02:38 +0100 Subject: [PATCH 23/33] feat: add locales for resources components --- packages/app-elements/src/locales/en.ts | 45 +++++++- packages/app-elements/src/locales/it.ts | 45 +++++++- packages/app-elements/src/mocks/stubs.ts | 15 +++ .../ResourceAddress/ResourceAddress.test.tsx | 61 ++++++---- .../ResourceAddress/ResourceAddress.tsx | 3 +- .../ResourceAddress/ResourceAddressForm.tsx | 4 +- .../ResourceAddressFormFields.tsx | 61 +++++++--- .../useResourceAddressOverlay.tsx | 8 +- .../ResourceDetails/ResourceDetailsForm.tsx | 10 +- .../ResourceDetails/useEditDetailsOverlay.tsx | 5 +- .../ResourceLineItems.test.tsx | 18 +-- .../ResourceLineItems/ResourceLineItems.tsx | 15 +-- .../ResourceLineItems.test.tsx.snap | 46 ++++---- .../ResourceMetadata/ResourceMetadata.tsx | 9 +- .../ResourceMetadata/ResourceMetadataForm.tsx | 7 +- .../useResourceFilters/FieldTimeRange.tsx | 11 +- .../useResourceFilters/FiltersForm.tsx | 3 +- .../useResourceFilters/FiltersNav.test.tsx | 14 ++- .../useResourceFilters/FiltersNav.tsx | 5 +- .../useResourceFilters/FiltersSearchBar.tsx | 3 +- .../useResourceFilters/mockedInstructions.ts | 109 +++++++++++++----- 21 files changed, 364 insertions(+), 133 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 024a0335..4601a31d 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -8,7 +8,19 @@ const resources = { addresses: { name: 'Address', name_other: 'Addresses', - attributes: {} + attributes: { + billing_info: 'Billing info', + city: 'City', + company: 'Company', + country_code: 'Country', + first_name: 'First name', + last_name: 'Last name', + line_1: 'Address', + notes: 'Notes', + phone: 'Phone', + state_code: 'State', + zip_code: 'ZIP code' + } }, adjustments: { name: 'Adjustment', @@ -188,22 +200,27 @@ type IsStringLiteral<T> = const en = { common: { + add_another: 'Add another', add_resource: 'Add {{resource}}', add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', amount: 'Amount', apply: 'Apply', + apply_filters: 'Apply filters', back: 'Back', go_back: 'Go back', cancel: 'Cancel', clear_text: 'Clear text', close: 'Close', continue: 'Continue', + create: 'Create', create_resource: 'Create {{resource}}', currency: 'Currency', + custom_time_range: 'Custom Time Range', download_file: 'Download file', download_json: 'Download JSON', edit: 'Edit', + edit_details: 'Edit details', edit_resource: 'Edit {{resource}}', filters: 'Filters', from: 'From', @@ -213,14 +230,24 @@ const en = { manage_resource: 'Manage {{resource}}', metadata: 'Metadata', new: 'New', + no_address: 'No address', no_items: 'No items', + no_metadata: 'No metadata', no_results_found: 'No results found', + no_textsearch_filter_set: 'No textSearch filter set', not_authorized: 'Not authorized', not_authorized_description: 'You are not authorized to access this page.', not_handled: 'Not handled', + reference: 'Reference', + reference_origin: 'Reference origin', + remove: 'Remove', + restocked: 'Restocked', search: 'Search...', see_all: 'See all', + select: 'Select...', select_resource: 'Select {{resource}}', + swap: 'Swap', + unit_price: 'Unit price', update: 'Update', updated: 'Updated', view_logs: 'View logs', @@ -248,7 +275,8 @@ const en = { minimum: 'Minimum', maximum: 'Maximum', drag_here_or: 'drag and drop it here or', - browse_files: 'browse files' + browse_files: 'browse files', + required_field: 'Required field' }, routes: { missing_configuration: @@ -280,6 +308,19 @@ const en = { 'Dear customer,\nplease follow this link to checkout your order #{{number}: \n{{url}}\nThank you,\nThe {{organization}} team', share_whatsapp_text: 'Please follow this link to checkout your order *#{{number}}*: {{url}}' + }, + filters_instructions: { + order_status: 'Order status', + payment_status: 'Payment status', + fulfillment_status: 'Fulfillment status', + archived: 'Archived', + only_archived: 'Only archived', + hide_archived: 'Hide archived', + show_all: 'Show all, both archived and not', + time_range: 'Time range', + search: 'Search', + name: 'Name', + amount: 'Amount' } }, resources, diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 56e3966a..15a2c176 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -2,22 +2,27 @@ import type en from './en' const it: typeof en = { common: { + add_another: 'Aggiungi un altro', add_resource: 'Aggiungi {{resource}}', add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', all_items: 'Tutti gli elementi', amount: 'Importo', apply: 'Applica', + apply_filters: 'Applica filtri', back: 'Indietro', go_back: 'Torna indietro', cancel: 'Annulla', close: 'Chiudi', clear_text: 'Svuota testo', continue: 'Continua', + create: 'Crea', create_resource: 'Crea {{resource}}', currency: 'Valuta', + custom_time_range: 'Intervallo di tempo personalizzato', download_file: 'Scarica file', download_json: 'Scarica JSON', edit: 'Modifica', + edit_details: 'Modifica dettagli', edit_resource: 'Modifica {{resource}}', filters: 'Filtri', from: 'Dal', @@ -27,15 +32,25 @@ const it: typeof en = { manage_resource: 'Gestisci {{resource}}', metadata: 'Metadati', new: 'Nuovo', + no_address: 'Nessun indirizzo', no_items: 'Nessun elemento', + no_metadata: 'Nessun metadato', no_results_found: 'Nessun risultato trovato', + no_textsearch_filter_set: 'Nessun filtro di ricerca testuale impostato', not_authorized: 'Non autorizzato', not_authorized_description: 'Non sei autorizzato ad accedere a questa pagina.', not_handled: 'Non gestito', + reference: 'Referenza', + reference_origin: 'Origine referenza', + remove: 'Rimuovi', + restocked: 'Rifornito', search: 'Cerca...', see_all: 'Vedi tutti', + select: 'Seleziona...', select_resource: 'Seleziona {{resource}}', + swap: 'Scambia', + unit_price: 'Prezzo unitario', update: 'Aggiorna', updated: 'Aggiornato', view_logs: 'Visualizza i log', @@ -63,7 +78,8 @@ const it: typeof en = { minimum: 'Minimo', maximum: 'Massimo', drag_here_or: 'trascina qui o', - browse_files: 'seleziona file' + browse_files: 'seleziona file', + required_field: 'Campo richiesto' }, generic_select_autocomplete_hint: 'Digita per cercare più opzioni.', routes: { @@ -98,13 +114,38 @@ const it: typeof en = { 'Gentile cliente,\nClicca su questo link per completare il tuo ordine #{{number}}: {{url}}\n\nGrazie,\nIl team di {{organization}}', share_whatsapp_text: 'Apri questo link per completare il tuo ordine *#{{number}}*: {{url}}' + }, + filters_instructions: { + order_status: 'Stato ordine', + payment_status: 'Stato pagamento', + fulfillment_status: 'Stato spedizione', + archived: 'Archiviati', + only_archived: 'Solo archiviati', + hide_archived: 'Nascondi archiviati', + show_all: 'Mostra tutti, sia archiviati che non', + time_range: 'Intervallo di tempo', + search: 'Cerca', + name: 'Nome', + amount: 'Importo' } }, resources: { addresses: { name: 'Indirizzo', name_other: 'Indirizzi', - attributes: {} + attributes: { + billing_info: 'Informazione di fatturazione', + city: 'Città', + company: 'Azienda', + country_code: 'Paese', + first_name: 'Nome', + last_name: 'Cognome', + line_1: 'Indirizzo', + notes: 'Note', + phone: 'Telefono', + state_code: 'Provincia', + zip_code: 'CAP' + } }, adjustments: { name: 'Modifica', diff --git a/packages/app-elements/src/mocks/stubs.ts b/packages/app-elements/src/mocks/stubs.ts index e462fca2..256968b8 100644 --- a/packages/app-elements/src/mocks/stubs.ts +++ b/packages/app-elements/src/mocks/stubs.ts @@ -23,3 +23,18 @@ vi.stubGlobal(`IntersectionObserver`, MockIntersectionObserver) const intersectionEntry = { isIntersecting: true } as unknown as IntersectionObserverEntry + +vi.mock('react-i18next', () => ({ + // this mock makes sure any components using the translate hook can use it without a warning being shown + useTranslation: () => { + return { + t: (i18nKey: string) => i18nKey, + i18n: {} + } + } +})) + +vi.mock('i18next', () => ({ + // this mock makes sure any components using the translate hook can use it without a warning being shown + t: (i18nKey: string) => i18nKey +})) diff --git a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.test.tsx b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.test.tsx index ffc4cbb3..f7b2555a 100644 --- a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.test.tsx +++ b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.test.tsx @@ -100,8 +100,10 @@ describe('ResourceAddress', () => { fireEvent.click(editButton) }) - const saveAddressButton = getByText('Update address') - expect(getByText('Edit address')).toBeVisible() + const saveAddressButton = getByText( + 'common.update resources.addresses.name' + ) + expect(getByText('common.edit resources.addresses.name')).toBeVisible() expect(saveAddressButton).toBeVisible() await waitFor(() => { fireEvent.click(saveAddressButton) @@ -136,9 +138,11 @@ describe('ResourceAddress', () => { fireEvent.click(editButton) }) - expect(getByText('New address')).toBeVisible() + expect(getByText('common.new resources.addresses.name')).toBeVisible() - const createAddressButton = getByText('Create address') + const createAddressButton = getByText( + 'common.create resources.addresses.name' + ) expect(createAddressButton).toBeVisible() fireEvent.keyDown(getByText('Select...'), { @@ -149,31 +153,46 @@ describe('ResourceAddress', () => { fireEvent.click(getByText('Angola')) await waitFor(() => { - fireEvent.change(getByLabelText('First name'), { - target: { value: 'John' } - }) + fireEvent.change( + getByLabelText('resources.addresses.attributes.first_name'), + { + target: { value: 'John' } + } + ) - fireEvent.change(getByLabelText('Last name'), { - target: { value: 'Doe' } - }) + fireEvent.change( + getByLabelText('resources.addresses.attributes.last_name'), + { + target: { value: 'Doe' } + } + ) - fireEvent.change(getByLabelText('Address'), { - target: { value: 'Via tutti' } - }) + fireEvent.change( + getByLabelText('resources.addresses.attributes.line_1'), + { + target: { value: 'Via tutti' } + } + ) - fireEvent.change(getByLabelText('City'), { + fireEvent.change(getByLabelText('resources.addresses.attributes.city'), { target: { value: 'Milan' } }) - fireEvent.change(getByLabelText('State code'), { - target: { value: 'FR' } - }) + fireEvent.change( + getByLabelText('resources.addresses.attributes.state_code'), + { + target: { value: 'FR' } + } + ) - fireEvent.change(getByLabelText('ZIP code'), { - target: { value: '20090' } - }) + fireEvent.change( + getByLabelText('resources.addresses.attributes.zip_code'), + { + target: { value: '20090' } + } + ) - fireEvent.change(getByLabelText('Phone'), { + fireEvent.change(getByLabelText('resources.addresses.attributes.phone'), { target: { value: '-' } }) }) diff --git a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.tsx b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.tsx index f58aed77..1f91915b 100644 --- a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.tsx +++ b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddress.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { Hr } from '#ui/atoms/Hr' @@ -184,7 +185,7 @@ export const ResourceAddress = withSkeletonTemplate<ResourceAddressProps>( weight={title == null ? 'bold' : undefined} variant={title != null ? 'info' : undefined} > - no address + {t('common.no_address')} </Text> )} </div> diff --git a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressForm.tsx b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressForm.tsx index 91d24ddb..c85c966e 100644 --- a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressForm.tsx +++ b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressForm.tsx @@ -1,4 +1,5 @@ import { useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { withSkeletonTemplate } from '#ui/atoms/SkeletonTemplate' import { Spacer } from '#ui/atoms/Spacer' @@ -78,7 +79,8 @@ export const ResourceAddressForm = disabled={methods.formState.isSubmitting} className='w-full' > - {address == null ? 'Create' : 'Update'} address + {address == null ? t('common.create') : t('common.update')}{' '} + {t('resources.addresses.name')} </Button> <Spacer top='2'> <HookedValidationApiError apiError={apiError} /> diff --git a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressFormFields.tsx b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressFormFields.tsx index be2ededc..9f444f69 100644 --- a/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressFormFields.tsx +++ b/packages/app-elements/src/ui/resources/ResourceAddress/ResourceAddressFormFields.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { Grid } from '#ui/atoms/Grid' import { withSkeletonTemplate } from '#ui/atoms/SkeletonTemplate' import { Spacer } from '#ui/atoms/Spacer' @@ -47,7 +48,7 @@ export const getResourceAddressFormFieldsSchema = ({ ctx.addIssue({ code: 'custom', path: ['company'], - message: 'Required field' + message: t('common.forms.required_field') }) } } else { @@ -55,7 +56,7 @@ export const getResourceAddressFormFieldsSchema = ({ ctx.addIssue({ code: 'custom', path: ['first_name'], - message: 'Required field' + message: t('common.forms.required_field') }) } @@ -63,7 +64,7 @@ export const getResourceAddressFormFieldsSchema = ({ ctx.addIssue({ code: 'custom', path: ['last_name'], - message: 'Required field' + message: t('common.forms.required_field') }) } } @@ -120,21 +121,30 @@ export const ResourceAddressFormFields = <FieldRow columns='2'> <HookedInput name={`${namePrefix}first_name`} - label='First name' + label={t('resources.addresses.attributes.first_name')} + /> + <HookedInput + name={`${namePrefix}last_name`} + label={t('resources.addresses.attributes.last_name')} /> - <HookedInput name={`${namePrefix}last_name`} label='Last name' /> </FieldRow> )} {isCompanyVisible && ( <FieldRow columns='1'> - <HookedInput name={`${namePrefix}company`} label='Company' /> + <HookedInput + name={`${namePrefix}company`} + label={t('resources.addresses.attributes.company')} + /> </FieldRow> )} <FieldRow columns='1'> <div className='flex flex-col gap-2'> - <HookedInput name={`${namePrefix}line_1`} label='Address' /> + <HookedInput + name={`${namePrefix}line_1`} + label={t('resources.addresses.attributes.line_1')} + /> <HookedInput name={`${namePrefix}line_2`} /> </div> </FieldRow> @@ -144,25 +154,34 @@ export const ResourceAddressFormFields = </FieldRow> <FieldRow columns='1'> - <HookedInput name={`${namePrefix}city`} label='City' /> + <HookedInput + name={`${namePrefix}city`} + label={t('resources.addresses.attributes.city')} + /> </FieldRow> <FieldRow columns='1'> <div className='grid grid-cols-[2fr_1fr] gap-4'> <SelectStates namePrefix={namePrefix} /> - <HookedInput name={`${namePrefix}zip_code`} label='ZIP code' /> + <HookedInput + name={`${namePrefix}zip_code`} + label={t('resources.addresses.attributes.zip_code')} + /> </div> </FieldRow> <FieldRow columns='1'> - <HookedInput name={`${namePrefix}phone`} label='Phone' /> + <HookedInput + name={`${namePrefix}phone`} + label={t('resources.addresses.attributes.phone')} + /> </FieldRow> {showBillingInfo && ( <FieldRow columns='1'> <HookedInput name={`${namePrefix}billing_info`} - label='Billing info' + label={t('resources.addresses.attributes.billing_info')} /> </FieldRow> )} @@ -171,7 +190,7 @@ export const ResourceAddressFormFields = <FieldRow columns='1'> <HookedInputTextArea name={`${namePrefix}notes`} - label='Notes' + label={t('resources.addresses.attributes.notes')} rows={2} /> </FieldRow> @@ -212,13 +231,18 @@ const SelectCountry: React.FC<{ namePrefix: string }> = ({ namePrefix }) => { }, []) if (forceTextInput) { - return <HookedInput name={`${namePrefix}country_code`} label='Country' /> + return ( + <HookedInput + name={`${namePrefix}country_code`} + label={t('resources.addresses.attributes.country_code')} + /> + ) } return ( <HookedInputSelect name={`${namePrefix}country_code`} - label='Country' + label={t('resources.addresses.attributes.country_code')} key={countries?.length} initialValues={countries ?? []} pathToValue='value' @@ -264,13 +288,18 @@ const SelectStates: React.FC<{ namePrefix: string }> = ({ namePrefix }) => { states?.length === 0 || forceTextInput ) { - return <HookedInput name={`${namePrefix}state_code`} label='State code' /> + return ( + <HookedInput + name={`${namePrefix}state_code`} + label={t('resources.addresses.attributes.state_code')} + /> + ) } return ( <HookedInputSelect name={`${namePrefix}state_code`} - label='State' + label={t('resources.addresses.attributes.state_code')} key={`${countryCode}_${states?.length}`} initialValues={states ?? []} pathToValue='value' diff --git a/packages/app-elements/src/ui/resources/ResourceAddress/useResourceAddressOverlay.tsx b/packages/app-elements/src/ui/resources/ResourceAddress/useResourceAddressOverlay.tsx index f9722678..bf18ea28 100644 --- a/packages/app-elements/src/ui/resources/ResourceAddress/useResourceAddressOverlay.tsx +++ b/packages/app-elements/src/ui/resources/ResourceAddress/useResourceAddressOverlay.tsx @@ -1,4 +1,5 @@ import { useOverlay } from '#hooks/useOverlay' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { PageLayout } from '#ui/composite/PageLayout' import { type Address } from '@commercelayer/sdk' @@ -32,10 +33,13 @@ export const useResourceAddressOverlay = ({ canUser('update', 'addresses') && ( <Overlay> <PageLayout - title={title ?? `${address == null ? 'New' : 'Edit'} address`} + title={ + title ?? + `${address == null ? t('common.new') : t('common.edit')} ${t('resources.addresses.name').toLowerCase()}` + } minHeight={false} navigationButton={{ - label: 'Back', + label: t('common.back'), onClick: () => { close() } diff --git a/packages/app-elements/src/ui/resources/ResourceDetails/ResourceDetailsForm.tsx b/packages/app-elements/src/ui/resources/ResourceDetails/ResourceDetailsForm.tsx index ae948c0b..fde0d696 100644 --- a/packages/app-elements/src/ui/resources/ResourceDetails/ResourceDetailsForm.tsx +++ b/packages/app-elements/src/ui/resources/ResourceDetails/ResourceDetailsForm.tsx @@ -6,6 +6,7 @@ import { useForm } from 'react-hook-form' import { getResourceEndpoint } from '#helpers/resources' import { useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { Spacer } from '#ui/atoms/Spacer' import { zodResolver } from '@hookform/resolvers/zod' @@ -53,10 +54,13 @@ export const ResourceDetailsForm = withSkeletonTemplate<{ > <Spacer bottom='12'> <Spacer bottom='8'> - <HookedInput name='reference' label='Reference' /> + <HookedInput name='reference' label={t('common.reference')} /> </Spacer> <Spacer bottom='8'> - <HookedInput name='reference_origin' label='Reference origin' /> + <HookedInput + name='reference_origin' + label={t('common.reference_origin')} + /> </Spacer> </Spacer> <Button @@ -64,7 +68,7 @@ export const ResourceDetailsForm = withSkeletonTemplate<{ disabled={methods.formState.isSubmitting} className='w-full' > - Update + {t('common.update')} </Button> <Spacer top='2'> <HookedValidationApiError apiError={apiError} /> diff --git a/packages/app-elements/src/ui/resources/ResourceDetails/useEditDetailsOverlay.tsx b/packages/app-elements/src/ui/resources/ResourceDetails/useEditDetailsOverlay.tsx index f968fd13..47aab250 100644 --- a/packages/app-elements/src/ui/resources/ResourceDetails/useEditDetailsOverlay.tsx +++ b/packages/app-elements/src/ui/resources/ResourceDetails/useEditDetailsOverlay.tsx @@ -1,4 +1,5 @@ import { useOverlay } from '#hooks/useOverlay' +import { t } from '#providers/I18NProvider' import { PageLayout } from '#ui/composite/PageLayout' import { type FC, useCallback } from 'react' import { type ResourceDetailsProps } from './ResourceDetails' @@ -22,11 +23,11 @@ export function useEditDetailsOverlay(): DetailsOverlayHook { const { Overlay: OverlayElement, open, close } = useOverlay() const OverlayComponent = useCallback<FC<EditDetailsOverlayProps>>( - ({ title = 'Back', resource, onUpdated }) => { + ({ title = t('common.back'), resource, onUpdated }) => { return ( <OverlayElement backgroundColor='light'> <PageLayout - title='Edit details' + title={t('common.edit_details')} minHeight={false} navigationButton={{ label: title, diff --git a/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.test.tsx b/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.test.tsx index 835c0371..53faab8e 100644 --- a/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.test.tsx +++ b/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.test.tsx @@ -89,7 +89,9 @@ describe('ResourceLineItems', () => { expect(within(secondRow).getByText('x 2')).toBeInTheDocument() expect(within(secondRow).getByText('x 2')).not.toHaveClass('hidden') expect(within(secondRow).getByText('18.00€')).toBeInTheDocument() - expect(within(secondRow).getByText('Unit price 9.00€')).toBeInTheDocument() + expect( + within(secondRow).getByText('common.unit_price 9.00€') + ).toBeInTheDocument() expect(within(thirdRow).getByRole('cell').children.length).toEqual(0) expect(within(fourthRow).getByRole('cell').children.length).toEqual(0) }) @@ -210,7 +212,9 @@ describe('ResourceLineItems', () => { expect(within(secondRow).getByText('x 1')).toBeInTheDocument() expect(within(secondRow).getByText('x 1')).not.toHaveClass('hidden') expect(within(secondRow).getByText('$10.00')).toBeInTheDocument() - expect(within(secondRow).getByText('Unit price $10.00')).toBeInTheDocument() + expect( + within(secondRow).getByText('common.unit_price $10.00') + ).toBeInTheDocument() expect(within(thirdRow).getByRole('cell').children.length).toEqual(1) expect(within(thirdRow).getByText('x 1')).toBeInTheDocument() expect( @@ -271,7 +275,7 @@ describe('ResourceLineItems', () => { expect(swapButton).not.toBeInTheDocument() const deleteButton: HTMLButtonElement = - within(thirdRow).getByLabelText('Delete') + within(thirdRow).getByLabelText('common.remove') expect(within(secondRow).getByText('x 2')).toHaveClass('hidden') expect(within(thirdRow).getByRole('cell').children.length).toEqual(1) @@ -323,8 +327,8 @@ describe('ResourceLineItems', () => { ) expect(container).toMatchSnapshot() - const swapButton = getAllByLabelText('Swap') - const deleteButton = getAllByLabelText('Delete') + const swapButton = getAllByLabelText('common.swap') + const deleteButton = getAllByLabelText('common.remove') expect(swapButton.length).toEqual(2) expect(deleteButton.length).toEqual(2) @@ -357,8 +361,8 @@ describe('ResourceLineItems', () => { ) expect(container).toMatchSnapshot() - const swapButton = getAllByLabelText('Swap') - const deleteButton = getAllByLabelText('Delete') + const swapButton = getAllByLabelText('common.swap') + const deleteButton = getAllByLabelText('common.remove') expect(swapButton.length).toEqual(1) expect(deleteButton.length).toEqual(1) diff --git a/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.tsx b/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.tsx index 2dba1bd6..3724853b 100644 --- a/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.tsx +++ b/packages/app-elements/src/ui/resources/ResourceLineItems/ResourceLineItems.tsx @@ -2,6 +2,7 @@ import { getStockTransferDisplayStatus } from '#dictionaries/stockTransfers' import { navigateTo } from '#helpers/appsNavigation' import { formatDateWithPredicate } from '#helpers/date' import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Avatar } from '#ui/atoms/Avatar' import { Badge } from '#ui/atoms/Badge' @@ -58,7 +59,7 @@ const Edit = withSkeletonTemplate<{ const removeButton = ( <RemoveButton - aria-label='Delete' + aria-label={t('common.remove')} disabled={disabled || removeDisabled} onClick={() => { if (!disabled) { @@ -70,7 +71,7 @@ const Edit = withSkeletonTemplate<{ } }} > - Remove + {t('common.remove')} </RemoveButton> ) @@ -102,7 +103,7 @@ const Edit = withSkeletonTemplate<{ <Button variant='link' className={cn(['flex items-center'])} - aria-label='Swap' + aria-label={t('common.swap')} disabled={disabled} onClick={() => { if (!disabled) { @@ -111,7 +112,7 @@ const Edit = withSkeletonTemplate<{ }} > <Swap size={18} weight='bold' /> - <span className='pl-1'>Swap</span> + <span className='pl-1'>{t('common.swap')}</span> </Button> )} {canRemove && ( @@ -316,7 +317,7 @@ export const ResourceLineItems = withSkeletonTemplate<Props>( {lineItem.type === 'line_items' && lineItem.formatted_unit_amount != null && ( <Spacer top='2'> - <Badge variant='secondary'>{`Unit price ${lineItem.formatted_unit_amount}`}</Badge> + <Badge variant='secondary'>{`${t('common.unit_price')} ${lineItem.formatted_unit_amount}`}</Badge> </Spacer> )} {lineItem.type === 'return_line_items' && @@ -326,7 +327,7 @@ export const ResourceLineItems = withSkeletonTemplate<Props>( <div className='flex items-center gap-1'> <Checks size={16} className='text-gray-500' />{' '} {formatDateWithPredicate({ - predicate: 'Restocked', + predicate: t('common.restocked'), isoDate: lineItem.restocked_at, timezone: user?.timezone })} @@ -337,7 +338,7 @@ export const ResourceLineItems = withSkeletonTemplate<Props>( {lineItem.type !== 'line_items' && 'bundle_code' in lineItem && lineItem.bundle_code != null && ( - <Badge variant='secondary'>{`BUNDLE ${lineItem.bundle_code}`}</Badge> + <Badge variant='secondary'>{`${t('resources.bundles.name').toUpperCase()} ${lineItem.bundle_code}`}</Badge> )} </td> <td valign='top' align='right'> diff --git a/packages/app-elements/src/ui/resources/ResourceLineItems/__snapshots__/ResourceLineItems.test.tsx.snap b/packages/app-elements/src/ui/resources/ResourceLineItems/__snapshots__/ResourceLineItems.test.tsx.snap index 801d5218..93a8c67d 100644 --- a/packages/app-elements/src/ui/resources/ResourceLineItems/__snapshots__/ResourceLineItems.test.tsx.snap +++ b/packages/app-elements/src/ui/resources/ResourceLineItems/__snapshots__/ResourceLineItems.test.tsx.snap @@ -51,7 +51,7 @@ exports[`ResourceLineItems > should disable the remove action when \`onSwap\` is <div class="flex items-center gap-1" > - Unit price 9.00€ + common.unit_price 9.00€ </div> </div> </div> @@ -155,7 +155,7 @@ exports[`ResourceLineItems > should disable the remove action when \`onSwap\` is class="flex gap-4 flex-col md:flex-row" > <button - aria-label="Swap" + aria-label="common.swap" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -172,16 +172,16 @@ exports[`ResourceLineItems > should disable the remove action when \`onSwap\` is <span class="pl-1" > - Swap + common.swap </span> </button> <span aria-description="Can't remove the last item" class="cursor-pointer" - data-tooltip-id="remove-cantremovethelastitem-top-end" + data-tooltip-id="commonremove-cantremovethelastitem-top-end" > <button - aria-label="Delete" + aria-label="common.remove" class="flex items-center rounded whitespace-nowrap leading-5 opacity-50 pointer-events-none touch-none inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" disabled="" > @@ -199,7 +199,7 @@ exports[`ResourceLineItems > should disable the remove action when \`onSwap\` is <span class="pl-1" > - Remove + common.remove </span> </button> </span> @@ -272,7 +272,7 @@ exports[`ResourceLineItems > should render 1`] = ` <div class="flex items-center gap-1" > - Unit price 9.00€ + common.unit_price 9.00€ </div> </div> </div> @@ -370,7 +370,7 @@ exports[`ResourceLineItems > should render the Footer prop when defined 1`] = ` <div class="flex items-center gap-1" > - Unit price 9.00€ + common.unit_price 9.00€ </div> </div> </div> @@ -489,7 +489,7 @@ exports[`ResourceLineItems > should render the InputSpinner and the trash icon w <div class="flex items-center gap-1" > - Unit price 9.00€ + common.unit_price 9.00€ </div> </div> </div> @@ -593,7 +593,7 @@ exports[`ResourceLineItems > should render the InputSpinner and the trash icon w class="flex gap-4 flex-col md:flex-row" > <button - aria-label="Delete" + aria-label="common.remove" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -610,7 +610,7 @@ exports[`ResourceLineItems > should render the InputSpinner and the trash icon w <span class="pl-1" > - Remove + common.remove </span> </button> </div> @@ -682,7 +682,7 @@ exports[`ResourceLineItems > should render the bundle 1`] = ` <div class="flex items-center gap-1" > - Unit price $10.00 + common.unit_price $10.00 </div> </div> </div> @@ -835,7 +835,7 @@ exports[`ResourceLineItems > should render the list item options 1`] = ` <div class="flex items-center gap-1" > - Unit price 27.00€ + common.unit_price 27.00€ </div> </div> </div> @@ -1163,7 +1163,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <div class="flex items-center gap-1" > - Unit price 9.00€ + common.unit_price 9.00€ </div> </div> </div> @@ -1267,7 +1267,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de class="flex gap-4 flex-col md:flex-row" > <button - aria-label="Swap" + aria-label="common.swap" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -1284,11 +1284,11 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <span class="pl-1" > - Swap + common.swap </span> </button> <button - aria-label="Delete" + aria-label="common.remove" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -1305,7 +1305,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <span class="pl-1" > - Remove + common.remove </span> </button> </div> @@ -1366,7 +1366,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <div class="flex items-center gap-1" > - Unit price $10.00 + common.unit_price $10.00 </div> </div> </div> @@ -1526,7 +1526,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de class="flex gap-4 flex-col md:flex-row" > <button - aria-label="Swap" + aria-label="common.swap" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -1543,11 +1543,11 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <span class="pl-1" > - Swap + common.swap </span> </button> <button - aria-label="Delete" + aria-label="common.remove" class="flex items-center rounded whitespace-nowrap leading-5 inline w-fit underline text-primary hover:text-primary-light border-primary-light cursor-pointer" > <svg @@ -1564,7 +1564,7 @@ exports[`ResourceLineItems > should render the swap action when \`onSwap\` is de <span class="pl-1" > - Remove + common.remove </span> </button> </div> diff --git a/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadata.tsx b/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadata.tsx index ef8cbc7a..a12cbdb9 100644 --- a/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadata.tsx +++ b/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadata.tsx @@ -3,6 +3,7 @@ import { useEditMetadataOverlay } from '#hooks/useEditMetadataOverlay' import { useCoreApi } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { Icon } from '#ui/atoms/Icon' @@ -70,13 +71,15 @@ export const ResourceMetadata = withSkeletonTemplate<ResourceMetadataProps>( variant='secondary' size='mini' alignItems='center' - aria-label='Edit metadata' + aria-label={t('common.edit_resource', { + resource: t('common.metadata').toLowerCase() + })} onClick={() => { show() }} > <Icon name='pencilSimple' size='16' /> - Edit + {t('common.edit')} </Button> ) } @@ -105,7 +108,7 @@ export const ResourceMetadata = withSkeletonTemplate<ResourceMetadataProps>( ) ) : ( <Spacer top='4'> - <Text variant='info'>No metadata.</Text> + <Text variant='info'>{t('common.no_metadata')}.</Text> </Spacer> )} </Section> diff --git a/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadataForm.tsx b/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadataForm.tsx index 3d253c1d..73495adf 100644 --- a/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadataForm.tsx +++ b/packages/app-elements/src/ui/resources/ResourceMetadata/ResourceMetadataForm.tsx @@ -6,6 +6,7 @@ import { HookedValidationApiError } from '#ui/forms/ReactHookForm' import { useForm } from 'react-hook-form' import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { Icon } from '#ui/atoms/Icon' import { Section } from '#ui/atoms/Section' @@ -186,7 +187,7 @@ export const ResourceMetadataForm = withSkeletonTemplate<{ </div> </div> <button - aria-label='Remove' + aria-label={t('common.remove')} type='button' className='rounded' onClick={() => { @@ -209,13 +210,13 @@ export const ResourceMetadataForm = withSkeletonTemplate<{ size='small' alignItems='center' > - <Icon name='plus' /> Add another + <Icon name='plus' /> {t('common.add_another')} </Button> </Spacer> </Section> </Spacer> <Button type='submit' disabled={isSubmitting} className='w-full'> - Update + {t('common.update')} </Button> <Spacer top='2'> <HookedValidationApiError apiError={apiError} /> diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/FieldTimeRange.tsx b/packages/app-elements/src/ui/resources/useResourceFilters/FieldTimeRange.tsx index 0798e67e..c8e274ee 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/FieldTimeRange.tsx +++ b/packages/app-elements/src/ui/resources/useResourceFilters/FieldTimeRange.tsx @@ -1,5 +1,6 @@ import { formatDateRange } from '#helpers/date' import { useOverlay } from '#hooks/useOverlay' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { Spacer } from '#ui/atoms/Spacer' @@ -98,14 +99,14 @@ export function FieldTimeRange({ item }: FieldTimeRangeProps): JSX.Element { }) }} > - Apply + {t('common.apply')} </Button> } > <PageLayout - title='Custom Time Range' + title={t('common.custom_time_range')} navigationButton={{ - label: 'Back', + label: t('common.back'), onClick: () => { setValue('timeFrom', null) setValue('timeTo', null) @@ -117,7 +118,7 @@ export function FieldTimeRange({ item }: FieldTimeRangeProps): JSX.Element { <Spacer bottom='14'> <HookedInputDate name='timeFrom' - label='From' + label={t('common.from')} isClearable preventOpenOnFocus /> @@ -125,7 +126,7 @@ export function FieldTimeRange({ item }: FieldTimeRangeProps): JSX.Element { <Spacer bottom='14'> <HookedInputDate name='timeTo' - label='To' + label={t('common.to')} minDate={timeFrom ?? undefined} isClearable preventOpenOnFocus diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersForm.tsx b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersForm.tsx index 14e39f1f..f4d14a99 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersForm.tsx +++ b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersForm.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { Spacer } from '#ui/atoms/Spacer' import { HookedForm } from '#ui/forms/Form' @@ -109,7 +110,7 @@ function FiltersForm({ })} <div className='w-full sticky bottom-0 bg-gray-50 pb-8'> <Button type='submit' fullWidth> - Apply filters + {t('common.apply_filters')} </Button> </div> </HookedForm> diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.test.tsx b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.test.tsx index 0e00f2ed..136c3c6d 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.test.tsx +++ b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.test.tsx @@ -22,11 +22,15 @@ describe('FiltersNav', () => { expect(container).toBeVisible() // grouped by status_in - expect(getByText('Order status · 2')).toBeVisible() + expect( + getByText('common.filters_instructions.order_status · 2') + ).toBeVisible() // payment_status_eq is only one, so it should not be grouped - expect(getByText('Authorized')).toBeVisible() + expect( + getByText('resources.orders.attributes.payment_status.authorized') + ).toBeVisible() // grouped by market_in, we have 3 markets - expect(getByText('Markets · 3')).toBeVisible() + expect(getByText('resources.markets.name_other · 3')).toBeVisible() }) test('should render single resource name (relationship) when there is only 1 filter for relationship selected (InputResourceGroup component)', async () => { @@ -64,7 +68,9 @@ describe('FiltersNav', () => { /> ) - fireEvent.click(getByText('Authorized')) + fireEvent.click( + getByText('resources.orders.attributes.payment_status.authorized') + ) expect(onFilterClick).toHaveBeenCalledWith( 'payment_status_eq=authorized&status_in=placed&status_in=approved', 'payment_status_eq' diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.tsx b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.tsx index 6287eb42..ff79e7ea 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.tsx +++ b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersNav.tsx @@ -1,5 +1,6 @@ import { formatDateRange } from '#helpers/date' import { useCoreApi } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { ButtonFilter } from '#ui/atoms/ButtonFilter' import { SkeletonTemplate } from '#ui/atoms/SkeletonTemplate' @@ -236,7 +237,7 @@ export function FiltersNav({ {/* Main filter button */} {activeGroupCount > 0 ? ( <ButtonFilter - label={`Filters · ${activeGroupCount}`} + label={`${t('common.filters')} · ${activeGroupCount}`} icon='funnelSimple' onClick={() => { onLabelClickHandler() @@ -245,7 +246,7 @@ export function FiltersNav({ /> ) : ( <ButtonFilter - label='Filters' + label={t('common.filters')} icon='funnelSimple' onClick={() => { onLabelClickHandler() diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersSearchBar.tsx b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersSearchBar.tsx index 5e2447c6..1cbaaa04 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/FiltersSearchBar.tsx +++ b/packages/app-elements/src/ui/resources/useResourceFilters/FiltersSearchBar.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { SearchBar, type SearchBarProps } from '#ui/composite/SearchBar' import castArray from 'lodash/castArray' import isEmpty from 'lodash/isEmpty' @@ -75,7 +76,7 @@ function FiltersSearchBar({ } if (textPredicate == null) { - return <div>No textSearch filter set</div> + return <div>{t('common.no_textsearch_filter_set')}</div> } const textPredicateValue = adaptUrlQueryToFormValues({ queryString })[ diff --git a/packages/app-elements/src/ui/resources/useResourceFilters/mockedInstructions.ts b/packages/app-elements/src/ui/resources/useResourceFilters/mockedInstructions.ts index eafc23e4..c272863b 100644 --- a/packages/app-elements/src/ui/resources/useResourceFilters/mockedInstructions.ts +++ b/packages/app-elements/src/ui/resources/useResourceFilters/mockedInstructions.ts @@ -1,8 +1,9 @@ +import { t } from '#providers/I18NProvider' import { type FiltersInstructions } from './types' export const instructions: FiltersInstructions = [ { - label: 'Markets', + label: t('resources.markets.name_other'), type: 'options', sdk: { predicate: 'market_id_in' @@ -20,7 +21,7 @@ export const instructions: FiltersInstructions = [ } }, { - label: 'Order status', + label: t('common.filters_instructions.order_status'), type: 'options', sdk: { predicate: 'status_in', @@ -31,16 +32,28 @@ export const instructions: FiltersInstructions = [ props: { mode: 'multi', options: [ - { value: 'pending', label: 'Pending' }, - { value: 'placed', label: 'Placed' }, - { value: 'approved', label: 'Approved' }, - { value: 'cancelled', label: 'Cancelled' } + { + value: 'pending', + label: t('resources.orders.attributes.status.pending') + }, + { + value: 'placed', + label: t('resources.orders.attributes.status.placed') + }, + { + value: 'approved', + label: t('resources.orders.attributes.status.approved') + }, + { + value: 'cancelled', + label: t('resources.orders.attributes.status.cancelled') + } ] } } }, { - label: 'Payment Status', + label: t('common.filters_instructions.payment_status'), type: 'options', sdk: { predicate: 'payment_status_eq' @@ -50,18 +63,36 @@ export const instructions: FiltersInstructions = [ props: { mode: 'single', options: [ - { value: 'authorized', label: 'Authorized' }, - { value: 'paid', label: 'Paid' }, - { value: 'voided', label: 'Voided' }, - { value: 'refunded', label: 'Refunded' }, - { value: 'free', label: 'Free' }, - { value: 'unpaid', label: 'Unpaid' } + { + value: 'authorized', + label: t('resources.orders.attributes.payment_status.authorized') + }, + { + value: 'paid', + label: t('resources.orders.attributes.payment_status.paid') + }, + { + value: 'voided', + label: t('resources.orders.attributes.payment_status.voided') + }, + { + value: 'refunded', + label: t('resources.orders.attributes.payment_status.refunded') + }, + { + value: 'free', + label: t('resources.orders.attributes.payment_status.free') + }, + { + value: 'unpaid', + label: t('resources.orders.attributes.payment_status.unpaid') + } ] } } }, { - label: 'Fulfillment Status', + label: t('common.filters_instructions.fulfillment_status'), type: 'options', sdk: { predicate: 'fulfillment_status_in' @@ -71,16 +102,34 @@ export const instructions: FiltersInstructions = [ props: { mode: 'multi', options: [ - { value: 'unfulfilled', label: 'Unfulfilled' }, - { value: 'in_progress', label: 'In Progress' }, - { value: 'fulfilled', label: 'Fulfilled' }, - { value: 'not_required', label: 'Not Required' } + { + value: 'unfulfilled', + label: t( + 'resources.orders.attributes.fulfillment_status.unfulfilled' + ) + }, + { + value: 'in_progress', + label: t( + 'resources.orders.attributes.fulfillment_status.in_progress' + ) + }, + { + value: 'fulfilled', + label: t('resources.orders.attributes.fulfillment_status.fulfilled') + }, + { + value: 'not_required', + label: t( + 'resources.orders.attributes.fulfillment_status.not_required' + ) + } ] } } }, { - label: 'Archived', + label: t('common.filters_instructions.archived'), type: 'options', sdk: { predicate: 'archived_at_null', @@ -93,15 +142,21 @@ export const instructions: FiltersInstructions = [ props: { mode: 'single', options: [ - { value: 'only', label: 'Only archived' }, - { value: 'hide', label: 'Hide archived' }, - { value: 'show', label: 'Show all, both archived and not' } + { + value: 'only', + label: t('common.filters_instructions.only_archived') + }, + { + value: 'hide', + label: t('common.filters_instructions.hide_archived') + }, + { value: 'show', label: t('common.filters_instructions.show_all') } ] } } }, { - label: 'Time Range', + label: t('common.filters_instructions.time_range'), type: 'timeRange', sdk: { predicate: 'updated_at' @@ -111,7 +166,7 @@ export const instructions: FiltersInstructions = [ } }, { - label: 'Search', + label: t('common.filters_instructions.search'), type: 'textSearch', sdk: { predicate: 'number_or_email_cont' @@ -121,7 +176,7 @@ export const instructions: FiltersInstructions = [ } }, { - label: 'Name', + label: t('common.filters_instructions.name'), type: 'textSearch', sdk: { predicate: 'name_eq' @@ -131,7 +186,7 @@ export const instructions: FiltersInstructions = [ } }, { - label: 'Amount', + label: t('common.filters_instructions.amount'), type: 'currencyRange', sdk: { predicate: 'total_amount_cents' @@ -139,7 +194,7 @@ export const instructions: FiltersInstructions = [ render: { component: 'inputCurrencyRange', props: { - label: 'Amount' + label: t('common.filters_instructions.amount') } } } From db6926bcdce27b7b41fdc25b00e545262bc8109e Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:17:01 +0100 Subject: [PATCH 24/33] fix: add new translations keys for app customers --- packages/app-elements/src/locales/en.ts | 27 ++++++++++++++++++++++++- packages/app-elements/src/locales/it.ts | 27 ++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 4601a31d..2aa4472b 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -43,6 +43,7 @@ const resources = { name: 'Customer', name_other: 'Customers', attributes: { + email: 'Email', status: { prospect: 'Prospect', acquired: 'Acquired', @@ -202,6 +203,7 @@ const en = { common: { add_another: 'Add another', add_resource: 'Add {{resource}}', + update_resource: 'Update {{resource}}', add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', amount: 'Amount', @@ -225,6 +227,7 @@ const en = { filters: 'Filters', from: 'From', to: 'To', + info: 'Info', limit_reached: 'Limit reached', loading: 'Loading...', manage_resource: 'Manage {{resource}}', @@ -298,7 +301,11 @@ const en = { timeline: { name: 'Timeline', leave_a_note: 'Leave a note or comment', - only_staff_can_see: 'Only you and other staff can see comments' + only_staff_can_see: 'Only you and other staff can see comments', + resource_created: '{{resource}} created', + resource_updated: '{{resource}} updated', + order_placed: 'Order #{{number}} placed in {{orderMarket}}', + left_a_nome: 'left a note' }, links: { checkout_link_status: 'Checkout link is {{status}}!', @@ -325,6 +332,24 @@ const en = { }, resources, apps: { + customers: { + attributes: { + status: 'Status' + }, + details: { + registered: 'Registered', + guest: 'Guest', + newsletter: 'Newsletter', + subscribed: 'Subscribed', + type: 'Type', + wallet: 'Wallet' + }, + form: { + customer_group_label: 'Group', + customer_group_hint: 'The group to which this customer belongs', + email_hint: "The customer's email address" + } + }, orders: { attributes: { status: 'Status', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 15a2c176..fe3cdffa 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -19,6 +19,7 @@ const it: typeof en = { create_resource: 'Crea {{resource}}', currency: 'Valuta', custom_time_range: 'Intervallo di tempo personalizzato', + update_resource: 'Aggiorna {{resource}}', download_file: 'Scarica file', download_json: 'Scarica JSON', edit: 'Modifica', @@ -27,6 +28,7 @@ const it: typeof en = { filters: 'Filtri', from: 'Dal', to: 'Al', + info: 'Informazioni', limit_reached: 'Limite raggiunto', loading: 'Caricamento...', manage_resource: 'Gestisci {{resource}}', @@ -104,7 +106,11 @@ const it: typeof en = { name: 'Attività', leave_a_note: 'Lascia una nota o un commento', only_staff_can_see: - 'Solo tu e altri membri dello staff possono vedere i commenti' + 'Solo tu e altri membri dello staff possono vedere i commenti', + resource_created: '{{resource}} creato', + resource_updated: '{{resource}} aggiornato', + order_placed: 'Ordine #{{number}} piazzato su {{orderMarket}}', + left_a_nome: 'ha lasciato una nota' }, links: { checkout_link_status: 'Il link al checkout è {{status}}!', @@ -168,6 +174,7 @@ const it: typeof en = { name: 'Cliente', name_other: 'Clienti', attributes: { + email: 'Indirizzo Email', status: { prospect: 'Potenziale', acquired: 'Acquisito', @@ -310,6 +317,24 @@ const it: typeof en = { required_field: 'Campo obbligatorio' }, apps: { + customers: { + attributes: { + status: 'Stato' + }, + details: { + registered: 'Registrato', + guest: 'Ospite', + newsletter: 'Newsletter', + subscribed: 'Iscritto', + type: 'Tipo', + wallet: 'Portafoglio' + }, + form: { + customer_group_label: 'Gruppo', + customer_group_hint: 'Il gruppo di appartenenza del cliente', + email_hint: "L'indirizzo email del cliente" + } + }, orders: { attributes: { status: 'Stato', From ad44ccd706257c3630eabc841b3f3505bf591566 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:04:10 +0100 Subject: [PATCH 25/33] fix: add more keys of app-customers --- packages/app-elements/src/locales/en.ts | 12 +++++++++++- packages/app-elements/src/locales/it.ts | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 2aa4472b..e170e5d3 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -203,6 +203,7 @@ const en = { common: { add_another: 'Add another', add_resource: 'Add {{resource}}', + new_resource: 'New {{resource}}', update_resource: 'Update {{resource}}', add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', @@ -217,6 +218,7 @@ const en = { continue: 'Continue', create: 'Create', create_resource: 'Create {{resource}}', + created: 'Created', currency: 'Currency', custom_time_range: 'Custom Time Range', download_file: 'Download file', @@ -224,6 +226,8 @@ const en = { edit: 'Edit', edit_details: 'Edit details', edit_resource: 'Edit {{resource}}', + delete_resource: 'Delete {{resource}}', + delete: 'Delete', filters: 'Filters', from: 'From', to: 'To', @@ -287,6 +291,8 @@ const en = { loading_app_page: 'Loading app page...', page_not_found: 'Page not found', invalid_resource: 'Invalid {{resource}}', + invalid_resource_or_not_authorized: + '{{resource}} is invalid or you are not authorized to access this page.', we_could_not_find_page: 'We could not find the page you are looking for.', we_could_not_find_resource: 'We could not find the {{resource}} you are looking for.', @@ -342,7 +348,11 @@ const en = { newsletter: 'Newsletter', subscribed: 'Subscribed', type: 'Type', - wallet: 'Wallet' + wallet: 'Wallet', + groups: 'Groups', + confirm_customer_delete: 'Confirm that you want to delete {{email}}', + customer_cannot_be_deleted: + 'Customer cannot be deleted from our dashboard' }, form: { customer_group_label: 'Group', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index fe3cdffa..d23c9da4 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -4,6 +4,7 @@ const it: typeof en = { common: { add_another: 'Aggiungi un altro', add_resource: 'Aggiungi {{resource}}', + new_resource: 'Crea {{resource}}', add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', all_items: 'Tutti gli elementi', amount: 'Importo', @@ -17,6 +18,7 @@ const it: typeof en = { continue: 'Continua', create: 'Crea', create_resource: 'Crea {{resource}}', + created: 'Creato', currency: 'Valuta', custom_time_range: 'Intervallo di tempo personalizzato', update_resource: 'Aggiorna {{resource}}', @@ -25,6 +27,8 @@ const it: typeof en = { edit: 'Modifica', edit_details: 'Modifica dettagli', edit_resource: 'Modifica {{resource}}', + delete_resource: 'Elimina {{resource}}', + delete: 'Elimina', filters: 'Filtri', from: 'Dal', to: 'Al', @@ -90,6 +94,8 @@ const it: typeof en = { loading_app_page: 'Caricamento pagina app...', page_not_found: 'Pagina non trovata', invalid_resource: '{{resource}} non valida', + invalid_resource_or_not_authorized: + '{{resource}} assente oppure non sei autorizzato ad accedere a questa risorsa.', we_could_not_find_page: 'Non abbiamo trovato la pagina che stavi cercando.', we_could_not_find_resource: @@ -327,7 +333,11 @@ const it: typeof en = { newsletter: 'Newsletter', subscribed: 'Iscritto', type: 'Tipo', - wallet: 'Portafoglio' + wallet: 'Portafoglio', + groups: 'Gruppi', + confirm_customer_delete: 'Sei sicuro di voler eliminare {{email}}?', + customer_cannot_be_deleted: + 'Il cliente non può essere eliminato tramite la nostra dashboard' }, form: { customer_group_label: 'Gruppo', From 7beac900b24f317cdda78af296f5e868bfd02b20 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:27:10 +0100 Subject: [PATCH 26/33] fix: improve loading state in i18n provider --- packages/app-elements/src/main.ts | 1 + packages/app-elements/src/providers/I18NProvider.tsx | 3 ++- packages/app-elements/src/ui/composite/Routes/Routes.tsx | 6 +++++- packages/app-elements/src/ui/composite/Routes/index.tsx | 7 ++++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index 07dc1386..f32fc7e0 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -183,6 +183,7 @@ export { createRoute, createTypedRoute, GenericPageNotFound, + LoadingPage, Routes, type GetParams, type PageProps, diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx index 2e4c35e5..6e7ee840 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -1,3 +1,4 @@ +import { LoadingPage } from '#ui/composite/Routes/Routes' import i18n, { type i18n as I18nInstance } from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' import resourcesToBackend from 'i18next-resources-to-backend' @@ -55,7 +56,7 @@ export const I18NProvider: React.FC<I18NProviderProps> = ({ i18nInstance == null || (i18nInstance != null && !i18nInstance.isInitialized) ) { - return <div>Loading i18n</div> + return <LoadingPage /> } return <I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider> diff --git a/packages/app-elements/src/ui/composite/Routes/Routes.tsx b/packages/app-elements/src/ui/composite/Routes/Routes.tsx index 263b3248..ce98b2a5 100644 --- a/packages/app-elements/src/ui/composite/Routes/Routes.tsx +++ b/packages/app-elements/src/ui/composite/Routes/Routes.tsx @@ -107,7 +107,11 @@ function Route({ ) } -function LoadingPage({ overlay = false }: { overlay?: boolean }): JSX.Element { +export function LoadingPage({ + overlay = false +}: { + overlay?: boolean +}): JSX.Element { const { settings: { mode } } = useTokenProvider() diff --git a/packages/app-elements/src/ui/composite/Routes/index.tsx b/packages/app-elements/src/ui/composite/Routes/index.tsx index 58a15043..aab6ddf5 100644 --- a/packages/app-elements/src/ui/composite/Routes/index.tsx +++ b/packages/app-elements/src/ui/composite/Routes/index.tsx @@ -1,4 +1,9 @@ -export { GenericPageNotFound, Routes, type PageProps } from './Routes' +export { + GenericPageNotFound, + LoadingPage, + Routes, + type PageProps +} from './Routes' export { createRoute, createTypedRoute, From 3cb6e432a861d9af6c93937a915f7e8eba221feb Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:13:32 +0100 Subject: [PATCH 27/33] fix: add missing translations for app shipments --- packages/app-elements/src/locales/en.ts | 110 +++++++++++++++++++++- packages/app-elements/src/locales/it.ts | 116 +++++++++++++++++++++++- 2 files changed, 219 insertions(+), 7 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index e170e5d3..be6588ee 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -101,6 +101,22 @@ const resources = { name_other: 'Markets', attributes: {} }, + packages: { + name: 'Package', + name_other: 'Packages', + attributes: {} + }, + parcels: { + name: 'Parcel', + name_other: 'Parcels', + attributes: { + unit_of_weight: { + gr: 'grams', + lb: 'pound', + oz: 'once' + } + } + }, promotions: { name: 'Promotion', name_other: 'Promotions', @@ -157,6 +173,11 @@ const resources = { name_other: 'SKUs', attributes: {} }, + stock_locations: { + name: 'Stock location', + name_other: 'Stock locations', + attributes: {} + }, stock_transfers: { name: 'Stock transfer', name_other: 'Stock transfers', @@ -204,6 +225,7 @@ const en = { add_another: 'Add another', add_resource: 'Add {{resource}}', new_resource: 'New {{resource}}', + missing_resource: 'Missing {{resource}}', update_resource: 'Update {{resource}}', add_up_to: 'You can add up to {{limit}} {{resource}}.', all_items: 'All items', @@ -260,6 +282,7 @@ const en = { view_logs: 'View logs', view_api_docs: 'View API reference', time_range: 'Time Range', + saving: 'Saving...', empty_states: { not_found: 'Not found', generic_not_found: 'We could not find the resource you are looking for.', @@ -267,9 +290,11 @@ const en = { no_resource_yet: 'No {{resource}} yet!', create_the_first_resource: 'Add a new {{resource}} with the API, or use the CLI.', + no_resources_found_for_list: + 'There are no {{resources}} for the current list.', no_resource_found_for_organization: 'No {{resource}} found for this organization.', - no_resource_found_for_filters: + no_resources_found_for_filters: "We didn't find any {{resources}} matching the current filters selection." }, generic_select_autocomplete_hint: 'Type to search for more options.', @@ -444,8 +469,89 @@ const en = { } }, shipments: { + attributes: { + status: 'Status' + }, details: { - awaiting_stock_transfer: 'Awaiting stock transfer' + awaiting_stock_transfer: 'Awaiting stock transfer', + label_already_purchased: 'Shipping label already purchased', + get_rates_error: 'Unable to get rates', + purchase_label_error: + 'Could not purchase shipping label, please contact your carrier.', + select_rate: 'Select a shipping rate', + getting_rates: 'Getting rates...', + purchasing: 'Purchasing...', + not_in_packing: 'This shipment is not in packing status', + picking_list: 'Picking list', + awaiting_stock_transfers: 'Awaiting stock transfers', + ship_from: 'Ship from', + ship_to: 'Ship to', + origin: 'Origin', + weight: 'Weight' + }, + tasks: { + pending: 'Pending', + browse: 'Browse', + picking: 'Picking', + packing: 'Packing', + ready_to_ship: 'Ready to ship', + on_hold: 'On hold', + all_shipments: 'All shipments' + }, + actions: { + put_on_hold: 'Put on hold', + start_picking: 'Start picking', + start_packing: 'Start packing', + continue_packing: 'Continue', + set_back_to_picking: 'Back to picking', + set_back_to_packing: 'Back to packing', + set_ready_to_ship: 'Ready to ship', + set_shipped: 'Mark as shipped', + set_delivered: 'Mark as delivered', + purchase_label: 'Purchase label', + purchase_labels: 'Purchase labels' + }, + form: { + unit_of_weight: 'Unit of weight', + required_package: 'Please select a package', + invalid_weight: 'Please enter a weight', + invalid_unit_of_weight: 'Please select a unit of weight', + incoterms_rules: 'Incoterms rules', + select_option: 'Select an option', + delivery_confirmation: 'Delivery confirmations', + require_custom_forms: 'Require custom forms', + customs_info_type: 'The type of item you are sending', + content_explanation_hint: 'Insert a brief description', + customs_info_failed_delivery_label: + 'In case the shipment cannot be delivered', + customs_info_restriction_type_label: 'Requires any special treatment', + customs_info_customs_signer_label: 'Customs signer', + customs_info_confirm_checkbox_label: + 'I confirm the provided information is accurate', + required_custom_form_value: + 'Required when specifying a customs form value', + required_if_other_is_selected: 'Please specify if "other" is selected', + required_restriction_comments: + "Please add a comment or select 'none' as restriction type", + customs_info_customs_signer_signature: 'Signature', + customs_info_customs_signer_no_signature: 'No signature', + customs_info_type_merchandise: 'Merchandise', + customs_info_type_gift: 'Gift', + customs_info_type_documents: 'Documents', + customs_info_type_returned_goods: 'Returned goods', + customs_info_type_sample: 'Sample', + customs_info_type_other: 'Other', + customs_info_failed_delivery_return: 'Return', + customs_info_failed_delivery_abandon: 'Abandon', + customs_info_restriction_type_none: 'None', + customs_info_restriction_type_other: 'Other', + customs_info_restriction_type_quarantine: 'Quarantine', + customs_info_restriction_type_sanitary_phytosanitary_inspection: + 'Sanitary or Phytosanitary inspection', + no_packages_found: 'No packages found for current stock location', + select_package: 'Select a package', + packing_items: 'items', + pack_items: 'Pack · {{items}}' } }, promotions: { diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index d23c9da4..51787f48 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -5,6 +5,7 @@ const it: typeof en = { add_another: 'Aggiungi un altro', add_resource: 'Aggiungi {{resource}}', new_resource: 'Crea {{resource}}', + missing_resource: '{{resource}} mancante', add_up_to: 'Puoi aggiungere fino a {{limit}} {{resource}}.', all_items: 'Tutti gli elementi', amount: 'Importo', @@ -62,6 +63,7 @@ const it: typeof en = { view_logs: 'Visualizza i log', view_api_docs: 'Visualizza la documentazione API', time_range: 'Periodo', + saving: 'Salvataggio...', empty_states: { not_found: 'Non trovato', generic_not_found: 'La risorsa che cercavi non è esiste.', @@ -69,10 +71,12 @@ const it: typeof en = { no_resource_yet: 'Non esiste ancora nessun risorsa di tipo {{resource}}!', create_the_first_resource: 'Aggiungi una nuova risorsa di tipo {{resource}} tramite API, oppure usa la CLI.', + no_resources_found_for_list: + 'Non ci sono {{resources}} per questa lista.', no_resource_found_for_organization: 'Nessuna risorsa {{resource}} trovata per questa organizzazione.', - no_resource_found_for_filters: - "Non c'è {{resources}} che corrisponde ai filtri selezionati." + no_resources_found_for_filters: + 'Non ci sono {{resources}} che corrispondono ai filtri selezionati.' }, forms: { currency_code_not_valid: @@ -238,6 +242,22 @@ const it: typeof en = { name_other: 'Mercati', attributes: {} }, + packages: { + name: 'Imballo', + name_other: 'Imballi', + attributes: {} + }, + parcels: { + name: 'Collo', + name_other: 'Colli', + attributes: { + unit_of_weight: { + gr: 'Grammi', + lb: 'Libbra', + oz: 'Oncia' + } + } + }, promotions: { name: 'Promozione', name_other: 'Promozioni', @@ -294,6 +314,11 @@ const it: typeof en = { name_other: 'SKU', attributes: {} }, + stock_locations: { + name: 'Magazzino', + name_other: 'Magazzini', + attributes: {} + }, stock_transfers: { name: 'Trasferimento di magazzino', name_other: 'Trasferimenti di magazzino', @@ -424,14 +449,95 @@ const it: typeof en = { returns: { details: { - origin: 'Origine', - destination: 'Destinazione', + origin: 'Magazzino origine', + destination: 'Magazzino destinazione', to_destination: 'Verso' } }, shipments: { + attributes: { + status: 'Stato spedizione' + }, details: { - awaiting_stock_transfer: 'In attesa di trasferimento di magazzino' + awaiting_stock_transfer: 'In attesa di trasferimento di magazzino', + label_already_purchased: 'Etichetta già acquistata', + get_rates_error: 'Errore nel recupero delle tariffe', + purchase_label_error: + "Errore nell'acquisto dell'etichetta di spedizione. Contatta il tuo corriere.", + select_rate: 'Seleziona una tariffa di spedizione', + getting_rates: 'Recupero tariffe...', + purchasing: 'Acquisto in corso...', + not_in_packing: 'La spedizione non è in stato di imballaggio', + picking_list: 'Lista di prelievo', + awaiting_stock_transfers: 'In attesa di trasferimenti di magazzino', + origin: 'Magazzino di partenza', + ship_from: 'Partenza da', + ship_to: 'Destinazione', + weight: 'Peso' + }, + tasks: { + pending: 'Aperte', + browse: 'Altro', + picking: 'Da prelevare', + packing: 'Da imballare', + ready_to_ship: 'Da spedire', + on_hold: 'In sospeso', + all_shipments: 'Tutte le spedizioni' + }, + actions: { + put_on_hold: 'Metti in sospeso', + start_picking: 'Inizia prelievo', + start_packing: 'Inizia imballaggio', + continue_packing: 'Continua imballaggio', + set_back_to_picking: 'Torna in stato prelievo', + set_back_to_packing: 'Torna in stato imballaggio', + set_ready_to_ship: 'Pronto per la spedizione', + set_shipped: 'Segna come spedito', + set_delivered: 'Segna come consegnato', + purchase_label: 'Acquista etichetta', + purchase_labels: 'Acquista etichette' + }, + form: { + unit_of_weight: 'Unità di peso', + required_package: 'Selezionare un imballo', + invalid_weight: 'Selezionare un peso valido', + invalid_unit_of_weight: "Selezionare un'unità di peso valida", + incoterms_rules: 'Regole Incoterms', + select_option: "Seleziona un'opzione", + delivery_confirmation: 'Conferma di consegna', + require_custom_forms: 'Richiedi moduli doganali', + customs_info_type: 'Tipo di merce che stai spedendo', + content_explanation_hint: 'Descrivi brevemente il contenuto del pacco', + customs_info_failed_delivery_label: + 'Istruzioni in caso di mancata consegna', + customs_info_restriction_type_label: 'Trattamenti speciali richiesti', + customs_info_customs_signer_label: 'Firma doganale', + customs_info_confirm_checkbox_label: + 'Confermo che le informazioni sono corrette', + required_custom_form_value: + 'Richiesto quando si specifica un valore del modulo doganale', + required_if_other_is_selected: 'Richiesto se "Altro" è selezionato', + required_restriction_comments: + 'Richiesto se si specificano restrizioni doganali', + customs_info_customs_signer_signature: 'Firma', + customs_info_customs_signer_no_signature: 'Nessuna firma', + customs_info_type_merchandise: 'Merce', + customs_info_type_gift: 'Regalo', + customs_info_type_documents: 'Documenti', + customs_info_type_returned_goods: 'Beni restituiti', + customs_info_type_sample: 'Campione', + customs_info_type_other: 'Altro', + customs_info_failed_delivery_return: 'Restituzione', + customs_info_failed_delivery_abandon: 'Abbandono', + customs_info_restriction_type_none: 'Nessuna restrizione', + customs_info_restriction_type_other: 'Altro', + customs_info_restriction_type_quarantine: 'Quarantena', + customs_info_restriction_type_sanitary_phytosanitary_inspection: + 'Ispezione sanitaria o fitosanitaria', + no_packages_found: 'Nessun imballo trovato in questo magazzino', + select_package: 'Seleziona un imballo', + packing_items: 'Prodotti', + pack_items: 'Imballa elementi · {{items}}' } }, promotions: { From a10a4d71f72da5c7f75b29eb4e2aa6dc65f355b6 Mon Sep 17 00:00:00 2001 From: Pier Francesco Ferrari <pierfrancesco@commercelayer.io> Date: Fri, 20 Dec 2024 13:10:39 +0100 Subject: [PATCH 28/33] chore: add locales for resources components --- packages/app-elements/src/locales/en.ts | 74 ++++++++- packages/app-elements/src/locales/it.ts | 74 ++++++++- .../ui/resources/ResourceOrderTimeline.tsx | 147 +++++++++++++----- .../resources/ResourcePaymentMethod.test.tsx | 16 +- .../ui/resources/ResourcePaymentMethod.tsx | 3 +- .../ResourceShipmentParcels.test.tsx | 64 ++++---- .../ui/resources/ResourceShipmentParcels.tsx | 49 +++--- .../src/ui/resources/ResourceTags.tsx | 9 +- .../useResourceList/useResourceList.tsx | 7 +- 9 files changed, 328 insertions(+), 115 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index be6588ee..321e30bb 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -232,24 +232,28 @@ const en = { amount: 'Amount', apply: 'Apply', apply_filters: 'Apply filters', + attachments: 'Attachments', back: 'Back', go_back: 'Go back', cancel: 'Cancel', clear_text: 'Clear text', close: 'Close', continue: 'Continue', + could_not_retrieve_data: 'Could not retrieve data', + could_not_retrieve_resource: 'Could not retrieve {{resource}}', create: 'Create', create_resource: 'Create {{resource}}', created: 'Created', currency: 'Currency', custom_time_range: 'Custom Time Range', + delete_resource: 'Delete {{resource}}', + delete: 'Delete', download_file: 'Download file', download_json: 'Download JSON', edit: 'Edit', edit_details: 'Edit details', edit_resource: 'Edit {{resource}}', - delete_resource: 'Delete {{resource}}', - delete: 'Delete', + estimated_delivery: 'Estimated delivery', filters: 'Filters', from: 'From', to: 'To', @@ -267,22 +271,31 @@ const en = { not_authorized: 'Not authorized', not_authorized_description: 'You are not authorized to access this page.', not_handled: 'Not handled', + parcel_total: 'Total', + parcel_weight: 'Weight', + print_shipping_label: 'Print shipping label', reference: 'Reference', reference_origin: 'Reference origin', + retry: 'Retry', remove: 'Remove', restocked: 'Restocked', search: 'Search...', see_all: 'See all', select: 'Select...', select_resource: 'Select {{resource}}', + show_less: 'Show less', + show_more: 'Show more', + saving: 'Saving...', + status: 'Status', swap: 'Swap', + time_range: 'Time Range', + tracking: 'Tracking', + try_to_refresh_page: 'Try to refresh the page or ask for support.', unit_price: 'Unit price', update: 'Update', updated: 'Updated', view_logs: 'View logs', view_api_docs: 'View API reference', - time_range: 'Time Range', - saving: 'Saving...', empty_states: { not_found: 'Not found', generic_not_found: 'We could not find the resource you are looking for.', @@ -336,7 +349,40 @@ const en = { resource_created: '{{resource}} created', resource_updated: '{{resource}} updated', order_placed: 'Order #{{number}} placed in {{orderMarket}}', - left_a_nome: 'left a note' + left_a_note: 'left a note', + left_a_refund_note: 'left a refund note', + resources: { + order_is: 'Order is', + order_was: 'Order was', + order_created: 'created', + order_placed: 'placed', + order_cancelled: 'cancelled', + order_archived: 'archived', + order_approved: 'approved', + order_fulfilled: 'fulfilled', + order_unfulfilled: 'unfulfilled', + order_fulfillment_is: 'Order fulfillment is', + order_fulfillment_in_progress: 'in progress', + order_fulfillment_not_required: 'not required', + payment_of_was: 'Payment of {{amount}} was', + return_number_was: 'Return #{{number}} was', + return_approved: 'approved', + return_cancelled: 'cancelled', + return_received: 'received', + return_rejected: 'rejected', + return_shipped: 'shipped', + stock_transfer_completed: 'completed', + shipment_number_is: 'Shipment #{{number}} is', + shipment_number_isbeing: 'Shipment #{{number}} is being', + shipment_number_was: 'Shipment #{{number}} was', + shipment_on_hold: 'on hold', + shipment_picked: 'picked', + shipment_packed: 'packed', + shipment_ready_to_ship: 'ready for shipping', + shipment_shipped: 'shipped', + transaction_of: '{{transaction}} of {{amount}}', + transaction_failed: 'failed' + } }, links: { checkout_link_status: 'Checkout link is {{status}}!', @@ -359,6 +405,24 @@ const en = { search: 'Search', name: 'Name', amount: 'Amount' + }, + tracking_details: { + contents_type: 'Contents type', + courier: 'Courier', + customs_signer: 'Customs signer', + customs_certify: 'Customs certify', + estimated_delivery_date: 'Estimated Delivery Date', + delivery_confirmation: 'Delivery confirmation', + last_update: 'Last update', + non_delivery_option: 'Non delivery option', + restriction_type: 'Restriction type', + tracking_pre_transit: 'Pre-Transit', + tracking_in_transit: 'In Transit', + tracking_out_for_delivery: 'Out for Delivery', + tracking_delivered: 'Delivered' + }, + no_resources: { + no_tags: 'No tags' } }, resources, diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 51787f48..c84e1c27 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -11,25 +11,29 @@ const it: typeof en = { amount: 'Importo', apply: 'Applica', apply_filters: 'Applica filtri', + attachments: 'Allegati', back: 'Indietro', go_back: 'Torna indietro', cancel: 'Annulla', close: 'Chiudi', clear_text: 'Svuota testo', continue: 'Continua', + could_not_retrieve_data: 'Impossibile recuperare i dati', + could_not_retrieve_resource: 'Impossibile recuperare {{resource}}', create: 'Crea', create_resource: 'Crea {{resource}}', created: 'Creato', currency: 'Valuta', custom_time_range: 'Intervallo di tempo personalizzato', update_resource: 'Aggiorna {{resource}}', + delete_resource: 'Elimina {{resource}}', + delete: 'Elimina', download_file: 'Scarica file', download_json: 'Scarica JSON', edit: 'Modifica', edit_details: 'Modifica dettagli', edit_resource: 'Modifica {{resource}}', - delete_resource: 'Elimina {{resource}}', - delete: 'Elimina', + estimated_delivery: 'Consegna stimata', filters: 'Filtri', from: 'Dal', to: 'Al', @@ -48,22 +52,31 @@ const it: typeof en = { not_authorized_description: 'Non sei autorizzato ad accedere a questa pagina.', not_handled: 'Non gestito', + parcel_total: 'Totale', + parcel_weight: 'Peso', + print_shipping_label: 'Stampa etichetta di spedizione', reference: 'Referenza', reference_origin: 'Origine referenza', remove: 'Rimuovi', restocked: 'Rifornito', + retry: 'Riprova', + saving: 'Salvataggio...', search: 'Cerca...', see_all: 'Vedi tutti', select: 'Seleziona...', select_resource: 'Seleziona {{resource}}', + show_less: 'Mostra meno', + show_more: 'Mostra di più', + status: 'Stato', swap: 'Scambia', + time_range: 'Periodo', + tracking: 'Tracciamento', + try_to_refresh_page: 'Prova a ricaricare la pagina o richiedi supporto.', unit_price: 'Prezzo unitario', update: 'Aggiorna', updated: 'Aggiornato', view_logs: 'Visualizza i log', view_api_docs: 'Visualizza la documentazione API', - time_range: 'Periodo', - saving: 'Salvataggio...', empty_states: { not_found: 'Non trovato', generic_not_found: 'La risorsa che cercavi non è esiste.', @@ -120,7 +133,40 @@ const it: typeof en = { resource_created: '{{resource}} creato', resource_updated: '{{resource}} aggiornato', order_placed: 'Ordine #{{number}} piazzato su {{orderMarket}}', - left_a_nome: 'ha lasciato una nota' + left_a_note: 'ha lasciato una nota', + left_a_refund_note: 'ha lasciato una nota di rimborso', + resources: { + order_is: "L'ordine è", + order_was: "L'Ordine è stato", + order_created: 'creato', + order_placed: 'piazzato', + order_cancelled: 'annullato', + order_archived: 'archiviato', + order_approved: 'approvato', + order_fulfilled: 'evaso', + order_unfulfilled: 'non evaso', + order_fulfillment_is: "L'evasione dell'ordine è", + order_fulfillment_in_progress: 'in corso', + order_fulfillment_not_required: 'non richiesto', + return_number_was: 'Reso #{{number}} era stato', + return_approved: 'approvato', + return_cancelled: 'annullato', + return_received: 'ricevuto', + return_rejected: 'rifiutato', + return_shipped: 'spedito', + payment_of_was: 'Il pagamento di {{amount}} era stato', + stock_transfer_completed: 'completato', + shipment_number_is: 'La spedizione #{{number}} è', + shipment_number_isbeing: 'La spedizione #{{number}} sta per essere', + shipment_number_was: 'La spedizione #{{number}} era', + shipment_on_hold: 'in attesa', + shipment_picked: 'presa', + shipment_packed: 'imballata', + shipment_ready_to_ship: 'pronta per la spedizione', + shipment_shipped: 'spedita', + transaction_of: '{{transaction}} di {{amount}}', + transaction_failed: 'fallito' + } }, links: { checkout_link_status: 'Il link al checkout è {{status}}!', @@ -143,6 +189,24 @@ const it: typeof en = { search: 'Cerca', name: 'Nome', amount: 'Importo' + }, + tracking_details: { + contents_type: 'Tipo di contenuto', + courier: 'Corriere', + customs_signer: 'Firmatario doganale', + customs_certify: 'Certificato doganale', + estimated_delivery_date: 'Data di consegna stimata', + delivery_confirmation: 'Conferma di consegna', + last_update: 'Ultimo aggiornamento', + non_delivery_option: 'Opzione di non consegna', + restriction_type: 'Tipo di restrizione', + tracking_pre_transit: 'Pre-Transito', + tracking_in_transit: 'In transito', + tracking_out_for_delivery: 'In consegna', + tracking_delivered: 'Consegnato' + }, + no_resources: { + no_tags: 'Nessun tag' } }, resources: { diff --git a/packages/app-elements/src/ui/resources/ResourceOrderTimeline.tsx b/packages/app-elements/src/ui/resources/ResourceOrderTimeline.tsx index e2159364..2881d48e 100644 --- a/packages/app-elements/src/ui/resources/ResourceOrderTimeline.tsx +++ b/packages/app-elements/src/ui/resources/ResourceOrderTimeline.tsx @@ -2,6 +2,7 @@ import { getOrderTransactionName } from '#dictionaries/orders' import { navigateTo } from '#helpers/appsNavigation' import { isAttachmentValidNote, referenceOrigins } from '#helpers/attachments' import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { withSkeletonTemplate } from '#ui/atoms/SkeletonTemplate' import { Text } from '#ui/atoms/Text' @@ -188,7 +189,10 @@ const useTimelineReducer = (order: Order) => { date: order.created_at, message: ( <> - Order was <Text weight='bold'>created</Text> + {t('common.timeline.resources.order_was')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_created')} + </Text> </> ) } @@ -207,7 +211,10 @@ const useTimelineReducer = (order: Order) => { date: order.placed_at, message: ( <> - Order was <Text weight='bold'>placed</Text> + {t('common.timeline.resources.order_was')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_placed')} + </Text> </> ) } @@ -226,7 +233,10 @@ const useTimelineReducer = (order: Order) => { date: order.cancelled_at, message: ( <> - Order was <Text weight='bold'>cancelled</Text> + {t('common.timeline.resources.order_was')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_cancelled')} + </Text> </> ) } @@ -245,7 +255,10 @@ const useTimelineReducer = (order: Order) => { date: order.archived_at, message: ( <> - Order was <Text weight='bold'>archived</Text> + {t('common.timeline.resources.order_was')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_archived')} + </Text> </> ) } @@ -264,7 +277,10 @@ const useTimelineReducer = (order: Order) => { date: order.approved_at, message: ( <> - Order was <Text weight='bold'>approved</Text> + {t('common.timeline.resources.order_was')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_approved')} + </Text> </> ) } @@ -283,22 +299,34 @@ const useTimelineReducer = (order: Order) => { const messages: Record<Order['fulfillment_status'], React.ReactNode> = { fulfilled: ( <> - Order was <Text weight='bold'>fulfilled</Text> + {t('common.timeline.resources.order_is')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_fulfilled')} + </Text> </> ), in_progress: ( <> - Order fulfillment is <Text weight='bold'>in progress</Text> + {t('common.timeline.resources.order_fulfillment_is')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_fulfillment_in_progress')} + </Text> </> ), not_required: ( <> - Order fulfillment is <Text weight='bold'>not required</Text> + {t('common.timeline.resources.order_fulfillment_is')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_fulfillment_not_required')} + </Text> </> ), unfulfilled: ( <> - Order is <Text weight='bold'>unfulfilled</Text> + {t('common.timeline.resources.order_is')}{' '} + <Text weight='bold'> + {t('common.timeline.resources.order_unfulfilled')} + </Text> </> ) } @@ -331,14 +359,21 @@ const useTimelineReducer = (order: Order) => { date: transaction.created_at, message: transaction.succeeded ? ( <> - Payment of {transaction.formatted_amount} was{' '} + {t('common.timeline.resources.payment_of_was', { + amount: transaction.formatted_amount + })}{' '} <Text weight='bold'>{name.pastTense}</Text> </> ) : ( <> {/* `Payment capture of xxxx failed` or `Authorization of xxxx failed`, etc... */} - {name.singular} of {transaction.formatted_amount}{' '} - <Text weight='bold'>failed</Text> + {t('common.timeline.resources.transaction_of', { + transaction: name.singular, + amount: transaction.formatted_amount + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.transaction_failed')} + </Text> </> ), note: @@ -375,8 +410,8 @@ const useTimelineReducer = (order: Order) => { <span> {attachment.reference_origin === referenceOrigins.appOrdersRefundNote - ? 'left a refund note' - : 'left a note'} + ? t('common.timeline.left_a_refund_note') + : t('common.timeline.left_a_note')} </span> ), note: attachment.description @@ -430,7 +465,9 @@ const useTimelineReducer = (order: Order) => { message: ( <> {stockTransferClickableLabel} {stockTransferFrom} - <Text weight='bold'>completed</Text> + <Text weight='bold'> + {t('common.timeline.resources.stock_transfer_completed')} + </Text> </> ) } @@ -455,8 +492,12 @@ const useTimelineReducer = (order: Order) => { date: shipment.on_hold_at, message: ( <> - Shipment #{shipment.number} is on{' '} - <Text weight='bold'>hold</Text> + {t('common.timeline.resources.shipment_number_is', { + number: shipment.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.shipment_on_hold')} + </Text> </> ) } @@ -470,8 +511,12 @@ const useTimelineReducer = (order: Order) => { date: shipment.picking_at, message: ( <> - Shipment #{shipment.number} was{' '} - <Text weight='bold'>picked</Text> + {t('common.timeline.resources.shipment_number_was', { + number: shipment.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.shipment_picked')} + </Text> </> ) } @@ -485,8 +530,12 @@ const useTimelineReducer = (order: Order) => { date: shipment.packing_at, message: ( <> - Shipment #{shipment.number} is being{' '} - <Text weight='bold'>packed</Text> + {t('common.timeline.resources.shipment_number_isbeing', { + number: shipment.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.shipment_packed')} + </Text> </> ) } @@ -500,8 +549,12 @@ const useTimelineReducer = (order: Order) => { date: shipment.ready_to_ship_at, message: ( <> - Shipment #{shipment.number} is{' '} - <Text weight='bold'>ready for shipping</Text> + {t('common.timeline.resources.shipment_number_is', { + number: shipment.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.shipment_ready_to_ship')} + </Text> </> ) } @@ -515,8 +568,12 @@ const useTimelineReducer = (order: Order) => { date: shipment.shipped_at, message: ( <> - Shipment #{shipment.number} was{' '} - <Text weight='bold'>shipped</Text> + {t('common.timeline.resources.shipment_number_was', { + number: shipment.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.shipment_shipped')} + </Text> </> ) } @@ -539,8 +596,12 @@ const useTimelineReducer = (order: Order) => { date: returnObj.approved_at, message: ( <> - Return #{returnObj.number} was{' '} - <Text weight='bold'>approved</Text> + {t('common.timeline.resources.return_number_was', { + number: returnObj.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.return_approved')} + </Text> </> ) } @@ -554,8 +615,12 @@ const useTimelineReducer = (order: Order) => { date: returnObj.cancelled_at, message: ( <> - Return #{returnObj.number} was{' '} - <Text weight='bold'>cancelled</Text> + {t('common.timeline.resources.return_number_was', { + number: returnObj.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.return_cancelled')} + </Text> </> ) } @@ -569,8 +634,12 @@ const useTimelineReducer = (order: Order) => { date: returnObj.shipped_at, message: ( <> - Return #{returnObj.number} was{' '} - <Text weight='bold'>shipped</Text> + {t('common.timeline.resources.return_number_was', { + number: returnObj.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.return_shipped')} + </Text> </> ) } @@ -584,8 +653,12 @@ const useTimelineReducer = (order: Order) => { date: returnObj.rejected_at, message: ( <> - Return #{returnObj.number} was{' '} - <Text weight='bold'>rejected</Text> + {t('common.timeline.resources.return_number_was', { + number: returnObj.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.return_rejected')} + </Text> </> ) } @@ -599,8 +672,12 @@ const useTimelineReducer = (order: Order) => { date: returnObj.received_at, message: ( <> - Return #{returnObj.number} was{' '} - <Text weight='bold'>received</Text> + {t('common.timeline.resources.return_number_was', { + number: returnObj.number + })}{' '} + <Text weight='bold'> + {t('common.timeline.resources.return_received')} + </Text> </> ) } diff --git a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx index 3d734e4e..a8f920ac 100644 --- a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx +++ b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx @@ -22,8 +22,8 @@ describe('ResourcePaymentMethod', () => { expect(getByText('··4242')).toBeVisible() // expandable content is not enabled - expect(queryByText('Show more')).not.toBeInTheDocument() - expect(queryByText('Show less')).not.toBeInTheDocument() + expect(queryByText('common.show_more')).not.toBeInTheDocument() + expect(queryByText('common.show_less')).not.toBeInTheDocument() }) it('should show the expandable content (payment_source) when `showPaymentResponse` is set', async () => { @@ -38,18 +38,18 @@ describe('ResourcePaymentMethod', () => { expect(getByText('··4242')).toBeVisible() // expandable content is enabled - expect(getByText('Show more')).toBeVisible() + expect(getByText('common.show_more')).toBeVisible() // show payment response block - fireEvent.click(getByText('Show more')) - expect(getByText('Show less')).toBeVisible() + fireEvent.click(getByText('common.show_more')) + expect(getByText('common.show_less')).toBeVisible() expect(getByText('resultCode:')).toBeVisible() expect(getByText('fraudResult:')).toBeVisible() // hide payment response block - fireEvent.click(getByText('Show less')) - expect(queryByText('Show more')).toBeVisible() - expect(queryByText('Show less')).not.toBeInTheDocument() + fireEvent.click(getByText('common.show_less')) + expect(queryByText('common.show_more')).toBeVisible() + expect(queryByText('common.show_less')).not.toBeInTheDocument() expect(queryByText('resultCode:')).not.toBeInTheDocument() expect(queryByText('fraudResult:')).not.toBeInTheDocument() }) diff --git a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx index ea9e659e..29b575f3 100644 --- a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx +++ b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx @@ -1,3 +1,4 @@ +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { Spacer } from '#ui/atoms/Spacer' import { Text } from '#ui/atoms/Text' @@ -114,7 +115,7 @@ export const ResourcePaymentMethod: FC<ResourcePaymentMethodProps> = ({ className='text-sm font-bold' type='button' > - {showMore ? 'Show less' : 'Show more'} + {showMore ? t('common.show_less') : t('common.show_more')} </Button> )} </div> diff --git a/packages/app-elements/src/ui/resources/ResourceShipmentParcels.test.tsx b/packages/app-elements/src/ui/resources/ResourceShipmentParcels.test.tsx index cd379196..65fb94a0 100644 --- a/packages/app-elements/src/ui/resources/ResourceShipmentParcels.test.tsx +++ b/packages/app-elements/src/ui/resources/ResourceShipmentParcels.test.tsx @@ -69,20 +69,20 @@ describe('ResourceShipmentParcels', () => { expect(parcelBox1).toBeVisible() expect(carrierBox).toHaveTextContent('€89,01') - expect(getValueByDetailName(carrierBox, 'Status')).toHaveTextContent( + expect(getValueByDetailName(carrierBox, 'common.status')).toHaveTextContent( 'delivered' ) - expect(getValueByDetailName(carrierBox, 'Tracking')).toHaveTextContent( - '42314321ASD4545' - ) expect( - getValueByDetailName(carrierBox, 'Estimated delivery') + getValueByDetailName(carrierBox, 'common.tracking') + ).toHaveTextContent('42314321ASD4545') + expect( + getValueByDetailName(carrierBox, 'common.estimated_delivery') ).toHaveTextContent('Jun 23, 2023 12:00 AM') - expect(() => getValueByDetailName(parcelBox1, 'Status')).toThrow() - expect(() => getValueByDetailName(parcelBox1, 'Tracking')).toThrow() + expect(() => getValueByDetailName(parcelBox1, 'common.status')).toThrow() + expect(() => getValueByDetailName(parcelBox1, 'common.tracking')).toThrow() expect(() => - getValueByDetailName(parcelBox1, 'Estimated delivery') + getValueByDetailName(parcelBox1, 'common.estimated_delivery') ).toThrow() }) @@ -111,25 +111,25 @@ describe('ResourceShipmentParcels', () => { expect(parcelBox2).toBeVisible() expect(carrierBox).toHaveTextContent('€89,01') - expect(getValueByDetailName(carrierBox, 'Status')).toHaveTextContent( + expect(getValueByDetailName(carrierBox, 'common.status')).toHaveTextContent( 'delivered' ) - expect(getValueByDetailName(carrierBox, 'Tracking')).toHaveTextContent( - '42314321ASD4545' - ) expect( - getValueByDetailName(carrierBox, 'Estimated delivery') + getValueByDetailName(carrierBox, 'common.tracking') + ).toHaveTextContent('42314321ASD4545') + expect( + getValueByDetailName(carrierBox, 'common.estimated_delivery') ).toHaveTextContent('Jun 23, 2023 12:00 AM') - expect(() => getValueByDetailName(parcelBox1, 'Status')).toThrow() - expect(() => getValueByDetailName(parcelBox2, 'Status')).toThrow() - expect(() => getValueByDetailName(parcelBox1, 'Tracking')).toThrow() - expect(() => getValueByDetailName(parcelBox2, 'Tracking')).toThrow() + expect(() => getValueByDetailName(parcelBox1, 'common.status')).toThrow() + expect(() => getValueByDetailName(parcelBox2, 'common.status')).toThrow() + expect(() => getValueByDetailName(parcelBox1, 'common.tracking')).toThrow() + expect(() => getValueByDetailName(parcelBox2, 'common.tracking')).toThrow() expect(() => - getValueByDetailName(parcelBox1, 'Estimated delivery') + getValueByDetailName(parcelBox1, 'common.estimated_delivery') ).toThrow() expect(() => - getValueByDetailName(parcelBox2, 'Estimated delivery') + getValueByDetailName(parcelBox2, 'common.estimated_delivery') ).toThrow() }) @@ -159,29 +159,29 @@ describe('ResourceShipmentParcels', () => { expect(carrierBox).toHaveTextContent('€12,00') - expect(() => getValueByDetailName(carrierBox, 'Status')).toThrow() - expect(() => getValueByDetailName(carrierBox, 'Tracking')).toThrow() + expect(() => getValueByDetailName(carrierBox, 'common.status')).toThrow() + expect(() => getValueByDetailName(carrierBox, 'common.tracking')).toThrow() expect(() => - getValueByDetailName(carrierBox, 'Estimated delivery') + getValueByDetailName(carrierBox, 'common.estimated_delivery') ).toThrow() - expect(getValueByDetailName(parcelBox1, 'Status')).toHaveTextContent( + expect(getValueByDetailName(parcelBox1, 'common.status')).toHaveTextContent( 'delivered' ) - expect(getValueByDetailName(parcelBox2, 'Status')).toHaveTextContent( + expect(getValueByDetailName(parcelBox2, 'common.status')).toHaveTextContent( 'in_transit' ) - expect(getValueByDetailName(parcelBox1, 'Tracking')).toHaveTextContent( - '43769811RQC9900' - ) - expect(getValueByDetailName(parcelBox2, 'Tracking')).toHaveTextContent( - '65345234RWQ1111' - ) expect( - getValueByDetailName(parcelBox1, 'Estimated delivery') + getValueByDetailName(parcelBox1, 'common.tracking') + ).toHaveTextContent('43769811RQC9900') + expect( + getValueByDetailName(parcelBox2, 'common.tracking') + ).toHaveTextContent('65345234RWQ1111') + expect( + getValueByDetailName(parcelBox1, 'common.estimated_delivery') ).toHaveTextContent('Jun 23, 2023 12:00 AM') expect( - getValueByDetailName(parcelBox2, 'Estimated delivery') + getValueByDetailName(parcelBox2, 'common.estimated_delivery') ).toHaveTextContent('Jun 23, 2023 12:00 AM') }) }) diff --git a/packages/app-elements/src/ui/resources/ResourceShipmentParcels.tsx b/packages/app-elements/src/ui/resources/ResourceShipmentParcels.tsx index 58cc3518..225108c8 100644 --- a/packages/app-elements/src/ui/resources/ResourceShipmentParcels.tsx +++ b/packages/app-elements/src/ui/resources/ResourceShipmentParcels.tsx @@ -10,6 +10,7 @@ import { type TrackingDetail } from '#helpers/tracking' import { useOverlay } from '#hooks/useOverlay' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { A } from '#ui/atoms/A' import { Avatar } from '#ui/atoms/Avatar' @@ -136,14 +137,14 @@ const Parcel = withSkeletonTemplate<{ <Spacer top='6'> <Text size='small'> <FlexRow> - <Text variant='info'>Total</Text> + <Text variant='info'>{t('common.parcel_total')}</Text> <Text weight='semibold'> {itemsLength} {itemsLength > 1 ? 'items' : 'item'} </Text> </FlexRow> <Spacer top='4'> <FlexRow> - <Text variant='info'>Weight</Text> + <Text variant='info'>{t('common.parcel_weight')}</Text> <Text weight='semibold'> {parcel.weight} {parcel.unit_of_weight} </Text> @@ -229,7 +230,7 @@ const Attachments = withSkeletonTemplate<{ return ( <FlexRow className='mt-4'> - <Text variant='info'>Attachments</Text> + <Text variant='info'>{t('common.attachments')}</Text> <Text weight='semibold'> <div className='flex flex-col gap-2 items-end'> {attachmentsWithUrl.map((attachment) => ( @@ -265,18 +266,18 @@ const Tracking = withSkeletonTemplate<{ <TrackingDetailsOverlay /> {trackingDetails?.status != null ? ( <FlexRow className='mt-4'> - <Text variant='info'>Status</Text> + <Text variant='info'>{t('common.status')}</Text> <Text weight='semibold'>{trackingDetails.status}</Text> </FlexRow> ) : parcel.tracking_status != null ? ( <FlexRow className='mt-4'> - <Text variant='info'>Status</Text> + <Text variant='info'>{t('common.status')}</Text> <Text weight='semibold'>{parcel.tracking_status}</Text> </FlexRow> ) : null} {parcel.tracking_number != null && ( <FlexRow className='mt-4'> - <Text variant='info'>Tracking</Text> + <Text variant='info'>{t('common.tracking')}</Text> <Text weight='semibold'> {trackingDetails != null ? ( <Button @@ -295,7 +296,7 @@ const Tracking = withSkeletonTemplate<{ )} {showEstimatedDelivery && rate != null && ( <FlexRow className='mt-4'> - <Text variant='info'>Estimated delivery</Text> + <Text variant='info'>{t('common.estimated_delivery')}</Text> <Text weight='semibold'>{rate.formatted_delivery_date}</Text> </FlexRow> )} @@ -318,7 +319,7 @@ const useTrackingDetails = (parcel: ParcelResource, rate?: Rate) => { parcel.tracking_number != null && ( <Overlay> <PageLayout - title={`Tracking #${parcel.tracking_number}`} + title={`${t('common.tracking')} #${parcel.tracking_number}`} navigationButton={{ label: 'Back', onClick: () => { @@ -372,19 +373,19 @@ const TrackingDetails = withSkeletonTemplate<{ <Steps steps={[ { - label: 'Pre-Transit', + label: t('common.tracking_details.tracking_pre_transit'), active: lastEvent?.tracking.status === 'pre_transit' }, { - label: 'In Transit', + label: t('common.tracking_details.tracking_in_transit'), active: lastEvent?.tracking.status === 'in_transit' }, { - label: 'Out for delivery', + label: t('common.tracking_details.tracking_out_for_delivery'), active: lastEvent?.tracking.status === 'out_for_delivery' }, { - label: 'Delivered', + label: t('common.tracking_details.tracking_delivered'), active: lastEvent?.tracking.status === 'delivered' } ]} @@ -394,7 +395,7 @@ const TrackingDetails = withSkeletonTemplate<{ <div> <Spacer bottom='2'> <Text size='small' tag='div' variant='info' weight='semibold'> - Courier + {t('common.tracking_details.courier')} </Text> </Spacer> {rate != null && ( @@ -414,7 +415,7 @@ const TrackingDetails = withSkeletonTemplate<{ <div> <Spacer bottom='2'> <Text size='small' tag='div' variant='info' weight='semibold'> - Estimated Delivery Date + {t('common.tracking_details.estimated_delivery_date')} </Text> </Spacer> <div className='text-lg font-semibold text-black'> @@ -434,7 +435,7 @@ const TrackingDetails = withSkeletonTemplate<{ border='none' actionButton={ <Text size='small' variant='info'> - Last update:{' '} + {t('common.tracking_details.last_update')}:{' '} <Text weight='bold'> {formatDate({ isoDate: lastEvent.date, @@ -509,7 +510,7 @@ const PrintLabel = withSkeletonTemplate<{ href: string }>(({ href }) => { <div className='text-center'> <A href={href}> <StatusIcon gap='small' className='text-2xl mr-1' name='printer' />{' '} - <Text size='small'>Print shipping label</Text> + <Text size='small'>{t('common.print_shipping_label')}</Text> </A> </div> ) @@ -545,7 +546,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( {!isEmpty(parcel.incoterm) && ( <FlexRow> <Text variant='info' wrap='nowrap'> - Iconterm + Incoterm </Text> <Text weight='semibold'>{parcel.incoterm}</Text> </FlexRow> @@ -555,7 +556,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( <Spacer top='4'> <FlexRow> <Text variant='info' wrap='nowrap'> - Delivery confirmation + {t('common.tracking_details.delivery_confirmation')} </Text> <Text weight='semibold'>{parcel.delivery_confirmation}</Text> </FlexRow> @@ -583,7 +584,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( <Spacer top='4'> <FlexRow> <Text variant='info' wrap='nowrap'> - Contents type + {t('common.tracking_details.contents_type')} </Text> <Text weight='semibold'> {/* `contents_explanation` is optional but if exists it means `contents_type` is set. So if it exists we give it priority */} @@ -597,7 +598,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( <Spacer top='4'> <FlexRow> <Text variant='info' wrap='nowrap'> - Non delivery option + {t('common.tracking_details.non_delivery_option')} </Text> <Text weight='semibold'>{parcel.non_delivery_option}</Text> </FlexRow> @@ -607,7 +608,9 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( {!isEmpty(parcel.restriction_type) && ( <Spacer top='4'> <FlexRow> - <Text variant='info'>Restriction type</Text> + <Text variant='info'> + {t('common.tracking_details.restriction_type')} + </Text> <Text weight='semibold'> {parcel.restriction_type}{' '} {parcel.restriction_comments != null @@ -622,7 +625,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( <Spacer top='4'> <FlexRow> <Text variant='info' wrap='nowrap'> - Customs signer + {t('common.tracking_details.customs_signer')} </Text> <Text weight='semibold'>{parcel.customs_signer}</Text> </FlexRow> @@ -633,7 +636,7 @@ const CustomsInfo = withSkeletonTemplate<{ parcel: ParcelResource }>( <Spacer top='4'> <FlexRow> <Text variant='info' wrap='nowrap'> - Customs certify + {t('common.tracking_details.customs_certify')} </Text> <Text weight='semibold'> {parcel.customs_certify === true ? 'Yes' : 'No'} diff --git a/packages/app-elements/src/ui/resources/ResourceTags.tsx b/packages/app-elements/src/ui/resources/ResourceTags.tsx index 04380f25..d75e9b0b 100644 --- a/packages/app-elements/src/ui/resources/ResourceTags.tsx +++ b/packages/app-elements/src/ui/resources/ResourceTags.tsx @@ -3,6 +3,7 @@ import { useEditTagsOverlay } from '#hooks/useEditTagsOverlay' import { useCoreApi } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { useTokenProvider } from '#providers/TokenProvider' import { Button } from '#ui/atoms/Button' import { Icon } from '#ui/atoms/Icon' @@ -86,21 +87,23 @@ export const ResourceTags = withSkeletonTemplate<ResourceTagsProps>( variant='secondary' size='mini' alignItems='center' - aria-label='Edit tags' + aria-label={t('common.edit_resource', { + resource: t('resources.tags.name').toLowerCase() + })} onClick={(e) => { e.preventDefault() show() }} > <Icon name='pencilSimple' size='16' /> - Edit + {t('common.edit')} </Button> ) } > {resourceTags == null || resourceTags.length === 0 ? ( <Spacer top='4'> - <Text variant='info'>No tags.</Text> + <Text variant='info'>{t('common.no_resources.no_tags')}.</Text> </Spacer> ) : ( <div className='flex flex-wrap gap-2 mt-4'> diff --git a/packages/app-elements/src/ui/resources/useResourceList/useResourceList.tsx b/packages/app-elements/src/ui/resources/useResourceList/useResourceList.tsx index ff86df23..4b015b6e 100644 --- a/packages/app-elements/src/ui/resources/useResourceList/useResourceList.tsx +++ b/packages/app-elements/src/ui/resources/useResourceList/useResourceList.tsx @@ -1,6 +1,7 @@ import { formatResourceName } from '#helpers/resources' import { useIsChanged } from '#hooks/useIsChanged' import { useCoreSdkProvider } from '#providers/CoreSdkProvider' +import { t } from '#providers/I18NProvider' import { Button } from '#ui/atoms/Button' import { Card } from '#ui/atoms/Card' import { EmptyState } from '#ui/atoms/EmptyState' @@ -252,7 +253,7 @@ export function useResourceList<TResource extends ListableResourceType>({ return ( <EmptyState title={`Could not retrieve ${type}`} - description='Try to refresh the page or ask for support.' + description={t('common.try_to_refresh_page')} /> ) } @@ -391,7 +392,7 @@ function ErrorLine({ <InputFeedback variant='danger' message={message} /> </Spacer> <Button size='small' onClick={onRetry}> - Retry + {t('common.retry')} </Button> </Spacer> ) @@ -400,7 +401,7 @@ function ErrorLine({ function parseApiErrorMessage(error: unknown): string { return CommerceLayerStatic.isApiError(error) ? (error.errors ?? []).map(({ detail }) => detail).join(', ') - : 'Could not retrieve data' + : t('common.could_not_retrieve_data') } /** From 11b33dc6e3b4a8cc75f3955d4340dd1d8f102c89 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:45:38 +0100 Subject: [PATCH 29/33] fix: export Trans component --- packages/app-elements/src/main.ts | 1 + packages/app-elements/src/providers/I18NProvider.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app-elements/src/main.ts b/packages/app-elements/src/main.ts index f32fc7e0..02fb8c70 100644 --- a/packages/app-elements/src/main.ts +++ b/packages/app-elements/src/main.ts @@ -65,6 +65,7 @@ export { i18nLocales, I18NProvider, t, + Trans, useTranslation, type I18NLocale } from '#providers/I18NProvider' diff --git a/packages/app-elements/src/providers/I18NProvider.tsx b/packages/app-elements/src/providers/I18NProvider.tsx index 6e7ee840..5dba7ef0 100644 --- a/packages/app-elements/src/providers/I18NProvider.tsx +++ b/packages/app-elements/src/providers/I18NProvider.tsx @@ -7,7 +7,7 @@ import { I18nextProvider, initReactI18next } from 'react-i18next' import type en from '../locales/en' export { t } from 'i18next' -export { useTranslation } from 'react-i18next' +export { Trans, useTranslation } from 'react-i18next' export const i18nLocales = ['en', 'it'] as const export type I18NLocale = (typeof i18nLocales)[number] From c3ee0dd031c94a51f9c9aa0e5cebb372b450f5da Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:47:04 +0100 Subject: [PATCH 30/33] fix: update locales for orders and returns --- .../app-elements/src/dictionaries/orders.ts | 10 ++--- packages/app-elements/src/locales/en.ts | 36 +++++++++++++++++- packages/app-elements/src/locales/it.ts | 37 +++++++++++++++++-- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index 158d23dc..d4a6276b 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -35,7 +35,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { label: t('resources.orders.attributes.status.placed'), icon: 'arrowDown', color: 'orange', - task: t('apps.orders.task.awaiting_approval') + task: t('apps.orders.tasks.awaiting_approval') } case 'placed:unpaid:unfulfilled': @@ -43,7 +43,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { label: t('resources.orders.attributes.status.placed'), icon: 'x', color: 'red', - task: t('apps.orders.task.error_to_cancel') + task: t('apps.orders.tasks.error_to_cancel') } case 'approved:authorized:unfulfilled': @@ -52,7 +52,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { label: t('resources.orders.attributes.status.approved'), icon: 'creditCard', color: 'orange', - task: t('apps.orders.task.payment_to_capture') + task: t('apps.orders.tasks.payment_to_capture') } case 'approved:paid:in_progress': @@ -61,7 +61,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { label: t('apps.orders.display_status.in_progress'), icon: 'arrowClockwise', color: 'orange', - task: t('apps.orders.task.fulfillment_in_progress') + task: t('apps.orders.tasks.fulfillment_in_progress') } case 'approved:authorized:in_progress': @@ -69,7 +69,7 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { label: t('apps.orders.display_status.in_progress_manual'), icon: 'arrowClockwise', color: 'orange', - task: t('apps.orders.task.fulfillment_in_progress') + task: t('apps.orders.tasks.fulfillment_in_progress') } case 'approved:paid:fulfilled': diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 321e30bb..ee2e5171 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -459,7 +459,7 @@ const en = { in_progress: 'In progress', in_progress_manual: 'In progress (Manual)' }, - task: { + tasks: { open: 'Open', browse: 'Browse', awaiting_approval: 'Awaiting approval', @@ -526,10 +526,42 @@ const en = { } }, returns: { + attributes: { + status: 'Status' + }, details: { origin: 'Origin', destination: 'Destination', - to_destination: 'To' + to_destination: 'To', + return_locations: 'Return locations', + confirm_return_cancellation: + 'Confirm that you want to cancel return #{{number}}', + delete_error: 'Could not cancel this return', + info: 'Info' + }, + tasks: { + open: 'Open', + browse: 'Browse', + requested: 'Requested', + approved: 'Approved', + shipped: 'Shipped', + all_returns: 'All returns', + archived: 'Archived' + }, + form: { + items: 'Items', + no_items: 'No items' + }, + actions: { + approve: 'Approve', + reject: 'Reject', + cancel: 'Cancel return', + ship: 'Mark shipped', + receive: 'Receive', + restock: 'Restock', + archive: 'Archive', + unarchive: 'Unarchive', + refund: 'Issue a refund' } }, shipments: { diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index c84e1c27..5b6f573c 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -444,7 +444,7 @@ const it: typeof en = { in_progress: 'In corso', in_progress_manual: 'In corso (Manuale)' }, - task: { + tasks: { open: 'Aperti', browse: 'Altro', awaiting_approval: 'Da approvare', @@ -510,12 +510,43 @@ const it: typeof en = { adjust_total: 'Modifica il totale' } }, - returns: { + attributes: { + status: 'Stato del reso' + }, details: { origin: 'Magazzino origine', destination: 'Magazzino destinazione', - to_destination: 'Verso' + to_destination: 'Verso', + return_locations: 'Magazzini di reso', + confirm_return_cancellation: + 'Sei sicuro di voler cancellare il reso #{{number}}', + delete_error: "Errore durante l'eliminazione del reso", + info: 'Informazioni' + }, + tasks: { + open: 'Aperti', + browse: 'Altro', + requested: 'Richiesti', + approved: 'Approvati', + shipped: 'Inviati', + all_returns: 'Tutti i resi', + archived: 'Archiviati' + }, + form: { + items: 'Prodotti', + no_items: 'Nessun prodotto' + }, + actions: { + approve: 'Approva', + reject: 'Rifiuta', + cancel: 'Annulla reso', + ship: 'Segna come spedito', + receive: 'Ricevuto', + restock: 'Rifornisci', + archive: 'Archivia', + unarchive: 'Ripristina', + refund: 'Emetti un rimborso' } }, shipments: { From 21192793a7abc1d92a2775a8127fa31c6ae4dea2 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:20:31 +0100 Subject: [PATCH 31/33] fix: add missing locale keys --- .../app-elements/src/dictionaries/orders.ts | 20 +++++++++------ packages/app-elements/src/locales/en.ts | 25 +++++++++++++++++-- packages/app-elements/src/locales/it.ts | 25 +++++++++++++++++-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index d4a6276b..d1a162cd 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -151,16 +151,20 @@ export function getOrderTransactionName( type: NonNullable<Order['transactions']>[number]['type'] ): { pastTense: string; singular: string } { const pastTenseDictionary: Record<typeof type, string> = { - authorizations: 'authorized', - captures: 'captured', - refunds: 'refunded', - voids: 'voided' + authorizations: t( + 'resources.orders.attributes.payment_status.authorized' + ).toLowerCase(), + captures: t('apps.orders.details.payment_captured').toLowerCase(), + refunds: t( + 'resources.orders.attributes.payment_status.refunded' + ).toLowerCase(), + voids: t('resources.orders.attributes.payment_status.voided').toLowerCase() } const singularDictionary: Record<typeof type, string> = { - authorizations: 'Payment authorization', - captures: 'Payment capture', - refunds: 'Refund', - voids: 'Void' + authorizations: t('apps.orders.details.payment_authorization'), + captures: t('apps.orders.details.payment_capture'), + refunds: t('apps.orders.details.payment_refund'), + voids: t('apps.orders.details.payment_void') } return { diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index ee2e5171..8c453043 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -299,6 +299,7 @@ const en = { empty_states: { not_found: 'Not found', generic_not_found: 'We could not find the resource you are looking for.', + all_good_here: 'All good here!', no_resource_found: 'No {{resource}} found!', no_resource_yet: 'No {{resource}} yet!', create_the_first_resource: @@ -494,7 +495,12 @@ const en = { 'Confirm that you want to cancel order #{{number}}', confirm_capture: 'Confirm capture', irreversible_action: - 'This action cannot be undone, proceed with caution.' + 'This action cannot be undone, proceed with caution.', + payment_captured: 'Captured', + payment_authorization: 'Payment authorization', + payment_capture: 'Payment capture', + payment_refund: 'Refund', + payment_void: 'Void' }, form: { language: 'Language', @@ -537,7 +543,22 @@ const en = { confirm_return_cancellation: 'Confirm that you want to cancel return #{{number}}', delete_error: 'Could not cancel this return', - info: 'Info' + info: 'Info', + timeline_requested_return: + '{{email}} requested the return of {{count} item', + timeline_requested_return_other: + '{{email}} requested the return of {{count} items', + timeline_shipped: 'Return was <strong>shipped</strong>.', + timeline_received: 'Return was <strong>received</strong>.', + timeline_cancelled: 'Return was <strong>cancelled</strong>.', + timeline_archived: 'Return was <strong>archived</strong>.', + timeline_approved: 'Return was <strong>approved</strong>.', + timeline_item_code_restocked: + 'Item {{code}} was <strong>restocked</strong>.', + timeline_payment_of_amount_was_action: + 'Payment of {{amount}} was <strong>{{action}}</strong>.', + timeline_action_of_amount_failed: + '{{action}} of {{amount}} <strong>failed</strong>.' }, tasks: { open: 'Open', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 5b6f573c..1a40ec01 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -80,6 +80,7 @@ const it: typeof en = { empty_states: { not_found: 'Non trovato', generic_not_found: 'La risorsa che cercavi non è esiste.', + all_good_here: 'Niente da fare qui!', no_resource_found: 'Nessuna risorsa {{resource}} trovata!', no_resource_yet: 'Non esiste ancora nessun risorsa di tipo {{resource}}!', create_the_first_resource: @@ -479,7 +480,12 @@ const it: typeof en = { "Sei sicuro di voler cancellare l'ordine #{{number}}", confirm_capture: 'Conferma cattura', irreversible_action: - 'Questa azione non può essere annullata, procedi con cautela.' + 'Questa azione non può essere annullata, procedi con cautela.', + payment_captured: 'Catturato', + payment_authorization: 'Autorizzazione pagamento', + payment_capture: 'Cattura pagamento', + payment_refund: 'Rimborso', + payment_void: 'Annulla' }, form: { language: 'Lingua', @@ -522,7 +528,22 @@ const it: typeof en = { confirm_return_cancellation: 'Sei sicuro di voler cancellare il reso #{{number}}', delete_error: "Errore durante l'eliminazione del reso", - info: 'Informazioni' + info: 'Informazioni', + timeline_requested_return: + '{{email}} ha richiesto un reso di {{count}} prodotto', + timeline_requested_return_other: + '{{email}} ha richiesto un reso di {{count}} prodotti', + timeline_shipped: 'Il resto è stato <strong>spedito</strong>.', + timeline_received: 'Il resto è stato <strong>ricevuto</strong>.', + timeline_cancelled: 'Il resto è stato <strong>cancellato</strong>.', + timeline_archived: 'Il resto è stato <strong>archiviato</strong>.', + timeline_approved: 'Il resto è stato <strong>approvato</strong>.', + timeline_item_code_restocked: + 'Il prodotto {{code}} è stato <strong>rifornito</strong>.', + timeline_payment_of_amount_was_action: + 'Il pagamento di {{amount}} è stato <strong>{{action}}</strong>.', + timeline_action_of_amount_failed: + 'Il tentativo di {{action}} di {{amount}} è <strong>fallito</strong>.' }, tasks: { open: 'Aperti', From 0bb1d28a29452035d2cbb5f11a9a05528f70472e Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:24:10 +0100 Subject: [PATCH 32/33] fix: update locale keys --- packages/app-elements/src/locales/en.ts | 16 ++++++++-------- packages/app-elements/src/locales/it.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/app-elements/src/locales/en.ts b/packages/app-elements/src/locales/en.ts index 8c453043..75b23d0f 100644 --- a/packages/app-elements/src/locales/en.ts +++ b/packages/app-elements/src/locales/en.ts @@ -548,17 +548,17 @@ const en = { '{{email}} requested the return of {{count} item', timeline_requested_return_other: '{{email}} requested the return of {{count} items', - timeline_shipped: 'Return was <strong>shipped</strong>.', - timeline_received: 'Return was <strong>received</strong>.', - timeline_cancelled: 'Return was <strong>cancelled</strong>.', - timeline_archived: 'Return was <strong>archived</strong>.', - timeline_approved: 'Return was <strong>approved</strong>.', + timeline_shipped: 'Return was <strong>shipped</strong>', + timeline_received: 'Return was <strong>received</strong>', + timeline_cancelled: 'Return was <strong>cancelled</strong>', + timeline_archived: 'Return was <strong>archived</strong>', + timeline_approved: 'Return was <strong>approved</strong>', timeline_item_code_restocked: - 'Item {{code}} was <strong>restocked</strong>.', + 'Item {{code}} was <strong>restocked</strong>', timeline_payment_of_amount_was_action: - 'Payment of {{amount}} was <strong>{{action}}</strong>.', + 'Payment of {{amount}} was <strong>{{action}}</strong>', timeline_action_of_amount_failed: - '{{action}} of {{amount}} <strong>failed</strong>.' + '{{action}} of {{amount}} <strong>failed</strong>' }, tasks: { open: 'Open', diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 1a40ec01..9163954e 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -533,17 +533,17 @@ const it: typeof en = { '{{email}} ha richiesto un reso di {{count}} prodotto', timeline_requested_return_other: '{{email}} ha richiesto un reso di {{count}} prodotti', - timeline_shipped: 'Il resto è stato <strong>spedito</strong>.', - timeline_received: 'Il resto è stato <strong>ricevuto</strong>.', - timeline_cancelled: 'Il resto è stato <strong>cancellato</strong>.', - timeline_archived: 'Il resto è stato <strong>archiviato</strong>.', - timeline_approved: 'Il resto è stato <strong>approvato</strong>.', + timeline_shipped: 'Il resto è stato <strong>spedito</strong>', + timeline_received: 'Il resto è stato <strong>ricevuto</strong>', + timeline_cancelled: 'Il resto è stato <strong>cancellato</strong>', + timeline_archived: 'Il resto è stato <strong>archiviato</strong>', + timeline_approved: 'Il resto è stato <strong>approvato</strong>', timeline_item_code_restocked: - 'Il prodotto {{code}} è stato <strong>rifornito</strong>.', + 'Il prodotto {{code}} è stato <strong>rifornito</strong>', timeline_payment_of_amount_was_action: - 'Il pagamento di {{amount}} è stato <strong>{{action}}</strong>.', + 'Il pagamento di {{amount}} è stato <strong>{{action}}</strong>', timeline_action_of_amount_failed: - 'Il tentativo di {{action}} di {{amount}} è <strong>fallito</strong>.' + 'Il tentativo di {{action}} di {{amount}} è <strong>fallito</strong>' }, tasks: { open: 'Aperti', From 481650dc0c6b06da07061e8c17fa760c1822d259 Mon Sep 17 00:00:00 2001 From: Giuseppe Ciotola <30926550+gciotola@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:05:15 +0100 Subject: [PATCH 33/33] fix: typos --- packages/app-elements/src/locales/it.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/app-elements/src/locales/it.ts b/packages/app-elements/src/locales/it.ts index 9163954e..0ec3adae 100644 --- a/packages/app-elements/src/locales/it.ts +++ b/packages/app-elements/src/locales/it.ts @@ -82,7 +82,8 @@ const it: typeof en = { generic_not_found: 'La risorsa che cercavi non è esiste.', all_good_here: 'Niente da fare qui!', no_resource_found: 'Nessuna risorsa {{resource}} trovata!', - no_resource_yet: 'Non esiste ancora nessun risorsa di tipo {{resource}}!', + no_resource_yet: + 'Non esiste ancora nessuna risorsa di tipo {{resource}}!', create_the_first_resource: 'Aggiungi una nuova risorsa di tipo {{resource}} tramite API, oppure usa la CLI.', no_resources_found_for_list: @@ -111,7 +112,7 @@ const it: typeof en = { 'Configurazione mancante durante la definizione di {{component}}', loading_app_page: 'Caricamento pagina app...', page_not_found: 'Pagina non trovata', - invalid_resource: '{{resource}} non valida', + invalid_resource: 'Risorsa {{resource}} non valida', invalid_resource_or_not_authorized: '{{resource}} assente oppure non sei autorizzato ad accedere a questa risorsa.', we_could_not_find_page: