From 2454b67005976ebe77698c7553a4ec66159b6e93 Mon Sep 17 00:00:00 2001 From: Marco Rieger Date: Wed, 12 Jun 2024 10:37:13 +0200 Subject: [PATCH] refactor(i18n): Switch to useLocalizedStringFormatter --- .pnp.cjs | 13 +++++++++++++ .../components/dev/test/locales/bar.locale.json | 5 ++++- .../components/dev/test/locales/foo.locale.json | 5 ++++- packages/components/dev/viteI18nPlugin.test.ts | 13 ++++++++----- packages/components/dev/viteI18nPlugin.ts | 16 ++++++++++++++-- packages/components/package.json | 1 + .../Footer/PaginationInfos/PaginationInfos.tsx | 14 +++++++------- .../components/TextFieldBase/TextFieldBase.tsx | 15 +++++++++------ .../lib/react/components/Translate/Translate.tsx | 6 +++--- yarn.lock | 12 +++++++++++- 10 files changed, 74 insertions(+), 26 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index ca94e70a6..1137d2f75 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -5937,6 +5937,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@internationalized/string-compiler", [\ + ["npm:3.2.4", {\ + "packageLocation": "./.yarn/cache/@internationalized-string-compiler-npm-3.2.4-1baecd6b79-751f3214b3.zip/node_modules/@internationalized/string-compiler/",\ + "packageDependencies": [\ + ["@internationalized/string-compiler", "npm:3.2.4"],\ + ["@formatjs/icu-messageformat-parser", "npm:2.7.8"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@isaacs/cliui", [\ ["npm:8.0.2", {\ "packageLocation": "./.yarn/cache/@isaacs-cliui-npm-8.0.2-f4364666d5-b1bf42535d.zip/node_modules/@isaacs/cliui/",\ @@ -6511,6 +6521,7 @@ const RAW_RUNTIME_STATE = ["@mittwald/flow-react-components", "virtual:c4e1b5de3c2221b63a822911bcf0be057e450c9772904486db938942efc684c035d907717e21ec2b40cde01712fe1164bb28cd81903edc8b86d34febd372a71c#workspace:packages/components"],\ ["@chakra-ui/live-region", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.1.0"],\ ["@faker-js/faker", "npm:8.4.1"],\ + ["@internationalized/string-compiler", "npm:3.2.4"],\ ["@mittwald/flow-design-tokens", "workspace:packages/design-tokens"],\ ["@mittwald/react-tunnel", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#workspace:packages/react-tunnel"],\ ["@mittwald/react-use-promise", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.3.13"],\ @@ -6613,6 +6624,7 @@ const RAW_RUNTIME_STATE = ["@mittwald/flow-react-components", "virtual:f09e910c1b508d3d7569e5bd3d1b58e6c8eedf483d3ecb00a35f3e6fff907cff61fcf2c7acd90c04d26e337186c1afc7265f77161911bcd5beabe7070df91545#workspace:packages/components"],\ ["@chakra-ui/live-region", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.1.0"],\ ["@faker-js/faker", "npm:8.4.1"],\ + ["@internationalized/string-compiler", "npm:3.2.4"],\ ["@mittwald/flow-design-tokens", "workspace:packages/design-tokens"],\ ["@mittwald/react-tunnel", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#workspace:packages/react-tunnel"],\ ["@mittwald/react-use-promise", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.3.13"],\ @@ -6710,6 +6722,7 @@ const RAW_RUNTIME_STATE = ["@mittwald/flow-react-components", "workspace:packages/components"],\ ["@chakra-ui/live-region", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.1.0"],\ ["@faker-js/faker", "npm:8.4.1"],\ + ["@internationalized/string-compiler", "npm:3.2.4"],\ ["@mittwald/flow-design-tokens", "workspace:packages/design-tokens"],\ ["@mittwald/react-tunnel", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#workspace:packages/react-tunnel"],\ ["@mittwald/react-use-promise", "virtual:ab0bf91802555465ebc7d7ba21ec0819758bb9ee11045ccd34f02a13730ce9f42ce681600fe46d0f4f24266bbd2a9c664d502c53968dc8e4b1c38c49b0728fef#npm:2.3.13"],\ diff --git a/packages/components/dev/test/locales/bar.locale.json b/packages/components/dev/test/locales/bar.locale.json index 4faaf81d5..f519c0cab 100644 --- a/packages/components/dev/test/locales/bar.locale.json +++ b/packages/components/dev/test/locales/bar.locale.json @@ -1 +1,4 @@ -{ "bar": "baz" } +{ + "bar": "test with variable {var}", + "bar.simple": "test simple variable" +} diff --git a/packages/components/dev/test/locales/foo.locale.json b/packages/components/dev/test/locales/foo.locale.json index 8c850a5fd..5c7bcd527 100644 --- a/packages/components/dev/test/locales/foo.locale.json +++ b/packages/components/dev/test/locales/foo.locale.json @@ -1 +1,4 @@ -{ "foo": "bar" } +{ + "foo": "bar {var}", + "foo.simple": "test simple variable" +} diff --git a/packages/components/dev/viteI18nPlugin.test.ts b/packages/components/dev/viteI18nPlugin.test.ts index b910a160a..88ee791b5 100644 --- a/packages/components/dev/viteI18nPlugin.test.ts +++ b/packages/components/dev/viteI18nPlugin.test.ts @@ -76,9 +76,11 @@ describe("vite i18n plugin", () => { expect(load).toBeDefined(); expect(load.code).toBeDefined(); expect(load.code).toMatchInlineSnapshot(` - "export default {"bar":{ "bar": "baz" } - ,"foo":{ "foo": "bar" } - };" + "export default {"bar": { "bar": (args) => \`test with variable \${args.var}\`, + "bar.simple": \`test simple variable\`, + },"foo": { "foo": (args) => \`bar \${args.var}\`, + "foo.simple": \`test simple variable\`, + }};" `); } }); @@ -110,8 +112,9 @@ describe("vite i18n plugin", () => { expect(load).toBeDefined(); expect(load.code).toBeDefined(); expect(load.code).toMatchInlineSnapshot(` - "export default {"bar":{ "bar": "baz" } - };" + "export default {"bar": { "bar": (args) => \`test with variable \${args.var}\`, + "bar.simple": \`test simple variable\`, + }};" `); } }); diff --git a/packages/components/dev/viteI18nPlugin.ts b/packages/components/dev/viteI18nPlugin.ts index e04bfb7c8..64e5416c4 100644 --- a/packages/components/dev/viteI18nPlugin.ts +++ b/packages/components/dev/viteI18nPlugin.ts @@ -2,6 +2,7 @@ import type { Plugin } from "vite"; import path from "path"; import * as fs from "fs"; import crypt from "crypto"; +import { compileStrings } from "@internationalized/string-compiler"; export const moduleSuffix = ".locale.json"; export const moduleId = `\x00${moduleSuffix}@`; @@ -19,6 +20,15 @@ export const generateVirtualFileId = (filePath: string): string => { return `${moduleId}${virtualFileId}`; }; +const compileLocalString = (localesString: string): string => { + return ( + compileStrings(JSON.parse(localesString)) + // we create our own virtual file, so we + // don't need the export from compileStrings + .replace("module.exports =", "") + ); +}; + const generateComponentIntlContent = ( filePath: string, languageKey: string, @@ -32,11 +42,13 @@ const generateComponentIntlContent = ( const match = filePath.match(importPathInfosRegEx); const fileContent = fs.readFileSync(filePath, "utf8"); - langObject.push(`"${match && match[3]}":${fileContent}`); + langObject.push( + `"${match && match[3]}":${compileLocalString(fileContent)}`, + ); }); } else { const fileContent = fs.readFileSync(filePath, "utf8"); - langObject.push(`"${languageKey}":${fileContent}`); + langObject.push(`"${languageKey}":${compileLocalString(fileContent)}`); } return `{${langObject.join(",")}}`; diff --git a/packages/components/package.json b/packages/components/package.json index 5f439cce0..ae4415831 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -275,6 +275,7 @@ }, "dependencies": { "@chakra-ui/live-region": "^2.1.0", + "@internationalized/string-compiler": "^3.2.4", "@mittwald/react-tunnel": "workspace:^", "@mittwald/react-use-promise": "^2.3.13", "@react-aria/utils": "^3.24.1", diff --git a/packages/components/src/components/List/components/Footer/PaginationInfos/PaginationInfos.tsx b/packages/components/src/components/List/components/Footer/PaginationInfos/PaginationInfos.tsx index 651e4aac8..01968d2ab 100644 --- a/packages/components/src/components/List/components/Footer/PaginationInfos/PaginationInfos.tsx +++ b/packages/components/src/components/List/components/Footer/PaginationInfos/PaginationInfos.tsx @@ -1,5 +1,5 @@ import locales from "../../../locales/*.locale.json"; -import { useMessageFormatter } from "react-aria"; +import { useLocalizedStringFormatter } from "react-aria"; import type { TextProps } from "@/components/Text"; import { Text } from "@/components/Text"; import type { FC } from "react"; @@ -8,7 +8,7 @@ import { useList } from "@/components/List/hooks/useList"; import { Skeleton } from "@/components/Skeleton"; export const PaginationInfos: FC = (props) => { - const stringFormatter = useMessageFormatter(locales); + const stringFormatter = useLocalizedStringFormatter(locales); const list = useList(); const pagination = list.batches; @@ -16,9 +16,9 @@ export const PaginationInfos: FC = (props) => { const isInitiallyLoading = list.loader.useIsInitiallyLoading(); const isEmpty = list.useIsEmpty(); - const totalItemsCount = pagination.getTotalItemsCount(); - const filteredItemsCount = pagination.getFilteredItemsCount(); - const visibleItemsCount = pagination.getVisibleItemsCount(); + const totalItemsCount = pagination.getTotalItemsCount() ?? 0; + const filteredItemsCount = pagination.getFilteredItemsCount() ?? 0; + const visibleItemsCount = pagination.getVisibleItemsCount() ?? 0; if (isEmpty) { return null; @@ -27,13 +27,13 @@ export const PaginationInfos: FC = (props) => { const text = isInitiallyLoading ? ( ) : isFiltered ? ( - stringFormatter("list.paginationInfoFiltered", { + stringFormatter.format("list.paginationInfoFiltered", { visibleItemsCount, filteredItemsCount, totalItemsCount, }) ) : ( - stringFormatter("list.paginationInfo", { + stringFormatter.format("list.paginationInfo", { visibleItemsCount, totalItemsCount, }) diff --git a/packages/components/src/components/TextFieldBase/TextFieldBase.tsx b/packages/components/src/components/TextFieldBase/TextFieldBase.tsx index 02bb3ce60..33d36ed77 100644 --- a/packages/components/src/components/TextFieldBase/TextFieldBase.tsx +++ b/packages/components/src/components/TextFieldBase/TextFieldBase.tsx @@ -8,7 +8,7 @@ import { ClearPropsContext, PropsContextProvider } from "@/lib/propsContext"; import { FieldError } from "@/components/FieldError"; import { FieldDescription } from "@/components/FieldDescription"; import locales from "./locales/*.locale.json"; -import { useMessageFormatter } from "react-aria"; +import { useLocalizedStringFormatter } from "react-aria"; export interface TextFieldBaseProps extends PropsWithChildren> { @@ -25,7 +25,7 @@ export const TextFieldBase = forwardRef( const rootClassName = clsx(styles.formField, className); - const translate = useMessageFormatter(locales); + const translation = useLocalizedStringFormatter(locales); const propsContext: PropsContext = { Label: { @@ -49,10 +49,13 @@ export const TextFieldBase = forwardRef( } }; - const charactersCountDescription = translate("textFieldBase.characters", { - count: charactersCount, - maxCount: props.maxLength ?? 0, - }); + const charactersCountDescription = translation.format( + "textFieldBase.characters", + { + count: charactersCount, + maxCount: props.maxLength ?? 0, + }, + ); return ( diff --git a/packages/components/src/lib/react/components/Translate/Translate.tsx b/packages/components/src/lib/react/components/Translate/Translate.tsx index 253fce58a..7203a3c32 100644 --- a/packages/components/src/lib/react/components/Translate/Translate.tsx +++ b/packages/components/src/lib/react/components/Translate/Translate.tsx @@ -1,6 +1,6 @@ import type { FC } from "react"; import type { LocalizedStrings } from "react-aria"; -import { useMessageFormatter } from "react-aria"; +import { useLocalizedStringFormatter } from "react-aria"; interface Props { locales: LocalizedStrings; @@ -10,8 +10,8 @@ interface Props { export const Translate: FC = (props) => { const { children, locales, variables } = props; - const formatter = useMessageFormatter(locales); - return formatter(children, variables); + const translator = useLocalizedStringFormatter(locales); + return translator.format(children, variables); }; export default Translate; diff --git a/yarn.lock b/yarn.lock index 154f39850..675df2179 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3504,7 +3504,7 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.7.8": +"@formatjs/icu-messageformat-parser@npm:2.7.8, @formatjs/icu-messageformat-parser@npm:^2.4.0": version: 2.7.8 resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8" dependencies: @@ -3583,6 +3583,15 @@ __metadata: languageName: node linkType: hard +"@internationalized/string-compiler@npm:^3.2.4": + version: 3.2.4 + resolution: "@internationalized/string-compiler@npm:3.2.4" + dependencies: + "@formatjs/icu-messageformat-parser": "npm:^2.4.0" + checksum: 10c0/751f3214b37e7facac67c26eb028da09061ae0d5bb11c814ac798e9fb87001f5c3ce4a071c24e79f1fbe47fdb56a1e04d78e23850b1fd6b319a61d720c6628ed + languageName: node + linkType: hard + "@internationalized/string@npm:^3.2.3": version: 3.2.3 resolution: "@internationalized/string@npm:3.2.3" @@ -4079,6 +4088,7 @@ __metadata: dependencies: "@chakra-ui/live-region": "npm:^2.1.0" "@faker-js/faker": "npm:^8.4.1" + "@internationalized/string-compiler": "npm:^3.2.4" "@mittwald/flow-design-tokens": "workspace:^" "@mittwald/react-tunnel": "workspace:^" "@mittwald/react-use-promise": "npm:^2.3.13"