diff --git a/front/bun.lock b/front/bun.lock index 6e2b6261..875b8781 100644 --- a/front/bun.lock +++ b/front/bun.lock @@ -12,6 +12,8 @@ "expo-build-properties": "^0.13.2", "expo-localization": "^16.0.1", "expo-updates": "~0.26.19", + "i18next-async-storage-backend": "^1.0.2", + "i18next-chained-backend": "^4.6.2", "i18next-fs-backend": "^2.6.0", "i18next-http-backend": "^3.0.2", "jotai": "^2.12.0", @@ -543,6 +545,8 @@ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], + "@react-native-community/async-storage": ["@react-native-community/async-storage@1.12.1", "", { "dependencies": { "deep-assign": "^3.0.0" }, "peerDependencies": { "react": "^16.8", "react-native": ">=0.59" } }, "sha512-70WGaH3PKYASi4BThuEEKMkyAgE9k7VytBqmgPRx3MzJx9/MkspwqJGmn3QLCgHLIFUgF1pit2mWICbRJ3T3lg=="], + "@react-native-community/cli": ["@react-native-community/cli@15.1.3", "", { "dependencies": { "@react-native-community/cli-clean": "15.1.3", "@react-native-community/cli-config": "15.1.3", "@react-native-community/cli-debugger-ui": "15.1.3", "@react-native-community/cli-doctor": "15.1.3", "@react-native-community/cli-server-api": "15.1.3", "@react-native-community/cli-tools": "15.1.3", "@react-native-community/cli-types": "15.1.3", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", "execa": "^5.0.0", "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.2", "semver": "^7.5.2" }, "bin": { "rnc-cli": "build/bin.js" } }, "sha512-+ih/WYUkJsEV2CMAnOHvVoSIz/Ahg5UJk+sqSIOmY79mWAglQzfLP71o7b0neJCnJWLmWiO6G6/S+kmULefD5g=="], "@react-native-community/cli-clean": ["@react-native-community/cli-clean@15.1.3", "", { "dependencies": { "@react-native-community/cli-tools": "15.1.3", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" } }, "sha512-3s9NGapIkONFoCUN2s77NYI987GPSCdr74rTf0TWyGIDf4vTYgKoWKKR+Ml3VTa1BCj51r4cYuHEKE1pjUSc0w=="], @@ -1061,6 +1065,8 @@ "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="], + "deep-assign": ["deep-assign@3.0.0", "", { "dependencies": { "is-obj": "^1.0.0" } }, "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], @@ -1327,6 +1333,10 @@ "i18next": ["i18next@24.2.2", "", { "dependencies": { "@babel/runtime": "^7.23.2" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ=="], + "i18next-async-storage-backend": ["i18next-async-storage-backend@1.0.2", "", { "peerDependencies": { "@react-native-community/async-storage": "*" } }, "sha512-jINQ+upKNwP8aHRxC0XUdbYKtSldXzsl1LZae1N7837SyVnO7ffojZzU+4PulMys9bHss4ETMIjVSjXXUqCvdw=="], + + "i18next-chained-backend": ["i18next-chained-backend@4.6.2", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-2P092fR+nAPQlGzPUoIIxbwo7PTBqQYgLxwv1XhSTQUAUoelLo5LkX+FqRxxSDg9WEAsrc8+2WL6mJtMGIa6WQ=="], + "i18next-fs-backend": ["i18next-fs-backend@2.6.0", "", {}, "sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw=="], "i18next-http-backend": ["i18next-http-backend@3.0.2", "", { "dependencies": { "cross-fetch": "4.0.0" } }, "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g=="], @@ -1387,6 +1397,8 @@ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="], + "is-path-cwd": ["is-path-cwd@2.2.0", "", {}, "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ=="], "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], diff --git a/front/package.json b/front/package.json index ff2ba729..78374423 100644 --- a/front/package.json +++ b/front/package.json @@ -24,6 +24,8 @@ "expo-build-properties": "^0.13.2", "expo-localization": "^16.0.1", "expo-updates": "~0.26.19", + "i18next-async-storage-backend": "^1.0.2", + "i18next-chained-backend": "^4.6.2", "i18next-fs-backend": "^2.6.0", "i18next-http-backend": "^3.0.2", "jotai": "^2.12.0", diff --git a/front/src/providers/account-provider.tsx b/front/src/providers/account-provider.tsx index c7a62c0d..dafc8f8f 100644 --- a/front/src/providers/account-provider.tsx +++ b/front/src/providers/account-provider.tsx @@ -4,6 +4,7 @@ import { Platform } from "react-native"; import { z } from "zod"; import { AccountP, UserP } from "~/models"; import { useFetch } from "~/query"; +import { AccountContext } from "./account-context"; import { removeAccounts, updateAccount } from "./account-store"; import { useSetError } from "./error-provider"; import { useStoreValue } from "./settings"; diff --git a/front/src/providers/index.tsx b/front/src/providers/index.tsx index 3de444d3..2e96311c 100644 --- a/front/src/providers/index.tsx +++ b/front/src/providers/index.tsx @@ -7,7 +7,7 @@ import { createQueryClient } from "~/query"; import { AccountProvider } from "./account-provider"; import { ErrorConsumer } from "./error-consumer"; import { ErrorProvider } from "./error-provider"; -import { TranslationsProvider } from "./translations"; +import { TranslationsProvider } from "./translations.web"; // const { TranslationsProvider } = // typeof window === "undefined" diff --git a/front/src/providers/translations.tsx b/front/src/providers/translations.tsx index 88058d3b..3296acaa 100644 --- a/front/src/providers/translations.tsx +++ b/front/src/providers/translations.tsx @@ -1,4 +1,8 @@ import i18next from "i18next"; +import AsyncStorageBackend, { + type AsyncStorageBackendOptions, +} from "i18next-async-storage-backend"; +import ChainedBackend, { type ChainedBackendOptions } from "i18next-chained-backend"; import HttpApi, { type HttpBackendOptions } from "i18next-http-backend"; import { getServerData } from "one"; import { type ReactNode, useMemo } from "react"; @@ -7,8 +11,7 @@ import { I18nextProvider } from "react-i18next"; export const TranslationsProvider = ({ children }: { children: ReactNode }) => { const val = useMemo(() => { const i18n = i18next.createInstance(); - // TODO: use https://github.com/i18next/i18next-browser-languageDetector - i18n.use(HttpApi).init({ + i18n.use(ChainedBackend).init({ interpolation: { escapeValue: false, }, @@ -17,7 +20,12 @@ export const TranslationsProvider = ({ children }: { children: ReactNode }) => { load: "currentOnly", supportedLngs: getServerData("supportedLngs"), backend: { - loadPath: "/translations/{{lng}}.json", + backends: [AsyncStorageBackend, HttpApi], + backendOptions: [ + { + loadPath: "/translations/{{lng}}.json", + } satisfies HttpBackendOptions, + ], }, }); i18n.services.resourceStore.data = getServerData("translations"); diff --git a/front/src/providers/translations.web.tsx b/front/src/providers/translations.web.tsx new file mode 100644 index 00000000..175430e1 --- /dev/null +++ b/front/src/providers/translations.web.tsx @@ -0,0 +1,28 @@ +import i18next from "i18next"; +import HttpApi, { type HttpBackendOptions } from "i18next-http-backend"; +import { getServerData } from "one"; +import { type ReactNode, useMemo } from "react"; +import { I18nextProvider } from "react-i18next"; + +export const TranslationsProvider = ({ children }: { children: ReactNode }) => { + const val = useMemo(() => { + const i18n = i18next.createInstance(); + // TODO: use https://github.com/i18next/i18next-browser-languageDetector + i18n.use(HttpApi).init({ + interpolation: { + escapeValue: false, + }, + returnEmptyString: false, + fallbackLng: "en", + load: "currentOnly", + supportedLngs: getServerData("supportedLngs"), + // we don't need to cache resources since we always get a fresh one from ssr + backend: { + loadPath: "/translations/{{lng}}.json", + }, + }); + i18n.services.resourceStore.data = getServerData("translations"); + return i18n; + }, []); + return {children}; +};