diff --git a/front/app.config.ts b/front/app.config.ts
index c962f03f..5a4b930c 100644
--- a/front/app.config.ts
+++ b/front/app.config.ts
@@ -1,4 +1,5 @@
import type { ExpoConfig } from "expo/config";
+import { supportedLanguages } from "~/providers/translations.compile";
const IS_DEV = process.env.APP_VARIANT === "development";
@@ -75,6 +76,12 @@ export const expo: ExpoConfig = {
},
},
],
+ [
+ "react-native-localization-settings",
+ {
+ languages: supportedLanguages,
+ }
+ ]
],
experiments: {
typedRoutes: true,
diff --git a/front/bun.lock b/front/bun.lock
index c2964e20..47c85f0e 100644
--- a/front/bun.lock
+++ b/front/bun.lock
@@ -28,6 +28,7 @@
"expo-splash-screen": "^31.0.10",
"expo-status-bar": "~3.0.8",
"expo-updates": "~29.0.11",
+ "i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jassub": "^1.8.6",
"langmap": "^0.0.16",
@@ -36,6 +37,7 @@
"react-i18next": "^16.1.0",
"react-native": "0.81.5",
"react-native-get-random-values": "^2.0.0",
+ "react-native-localization-settings": "^1.2.0",
"react-native-mmkv": "^3.3.3",
"react-native-nitro-modules": "^0.30.2",
"react-native-reanimated": "~4.1.2",
@@ -927,6 +929,8 @@
"i18next": ["i18next@25.6.0", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw=="],
+ "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
+
"i18next-http-backend": ["i18next-http-backend@3.0.2", "", { "dependencies": { "cross-fetch": "4.0.0" } }, "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
@@ -1259,6 +1263,8 @@
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
+ "react-native-localization-settings": ["react-native-localization-settings@1.2.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-pxX/mfokqjwIdb1zINuN6DLL4PeVHTaIGz2Tk833tS94fmpsSuPoYnkCmtXsfvZjxhDOSsRceao/JutJbIlpIQ=="],
+
"react-native-mmkv": ["react-native-mmkv@3.3.3", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-GMsfOmNzx0p5+CtrCFRVtpOOMYNJXuksBVARSQrCFaZwjUyHJdQzcN900GGaFFNTxw2fs8s5Xje//RDKj9+PZA=="],
"react-native-nitro-modules": ["react-native-nitro-modules@0.30.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+/uVS7FQwOiKYZQERMIvBRv5/X3CVHrFG6Nr/kIhVfVxGeUimHnBz7cgA97lJKIn7AKDRWL+UjLedW8pGOt0dg=="],
diff --git a/front/package.json b/front/package.json
index fccb6740..b5df9403 100644
--- a/front/package.json
+++ b/front/package.json
@@ -39,6 +39,7 @@
"expo-splash-screen": "^31.0.10",
"expo-status-bar": "~3.0.8",
"expo-updates": "~29.0.11",
+ "i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jassub": "^1.8.6",
"langmap": "^0.0.16",
@@ -47,6 +48,7 @@
"react-i18next": "^16.1.0",
"react-native": "0.81.5",
"react-native-get-random-values": "^2.0.0",
+ "react-native-localization-settings": "^1.2.0",
"react-native-mmkv": "^3.3.3",
"react-native-nitro-modules": "^0.30.2",
"react-native-reanimated": "~4.1.2",
diff --git a/front/packages/ui/src/settings/general.tsx b/front/packages/ui/src/settings/general.tsx
deleted file mode 100644
index 45ba61ed..00000000
--- a/front/packages/ui/src/settings/general.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Kyoo - A portable and vast media library solution.
- * Copyright (c) Kyoo.
- *
- * See AUTHORS.md and LICENSE file in the project root for full license information.
- *
- * Kyoo is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
- *
- * Kyoo is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Kyoo. If not, see .
- */
-
-import { deleteData, setUserTheme, storeData, useUserTheme } from "@kyoo/models";
-import { Link, Select } from "@kyoo/primitives";
-import { useTranslation } from "react-i18next";
-import { Preference, SettingsContainer } from "./base";
-
-import Theme from "@material-symbols/svg-400/outlined/dark_mode.svg";
-import Language from "@material-symbols/svg-400/outlined/language.svg";
-
-import Android from "@material-symbols/svg-400/rounded/android.svg";
-import Public from "@material-symbols/svg-400/rounded/public.svg";
-import { useLanguageName } from "../utils";
-
-export const GeneralSettings = () => {
- const { t, i18n } = useTranslation();
- const theme = useUserTheme("auto");
- const getLanguageName = useLanguageName();
-
- const changeLanguage = (lang: string) => {
- if (lang === "system") {
- i18n.changeLanguage(i18n.systemLanguage);
- deleteData("language");
- return;
- }
- storeData("language", lang);
- i18n.changeLanguage(lang);
- };
-
- return (
-
-
-
-
-
-
- );
-};
-
-export const About = () => {
- const { t } = useTranslation();
-
- return (
-
-
-
-
-
-
-
-
- );
-};
diff --git a/front/public/translations/en.json b/front/public/translations/en.json
index e2a5fbac..04dc6a41 100644
--- a/front/public/translations/en.json
+++ b/front/public/translations/en.json
@@ -128,8 +128,7 @@
},
"language": {
"label": "Language",
- "description": "Set the language of your application",
- "system": "System"
+ "description": "Set the language of your application"
}
},
"playback": {
diff --git a/front/src/providers/translations-detector.tsx b/front/src/providers/translations-detector.tsx
new file mode 100644
index 00000000..19a80c04
--- /dev/null
+++ b/front/src/providers/translations-detector.tsx
@@ -0,0 +1,3 @@
+import { createLanguageDetector } from "react-native-localization-settings";
+
+export const languageDetector = createLanguageDetector({});
diff --git a/front/src/providers/translations-detector.web.tsx b/front/src/providers/translations-detector.web.tsx
new file mode 100644
index 00000000..c003a4c3
--- /dev/null
+++ b/front/src/providers/translations-detector.web.tsx
@@ -0,0 +1,7 @@
+import LanguageDetector from "i18next-browser-languagedetector";
+
+export const languageDetector = new LanguageDetector(null, {
+ order: ["querystring", "cookie", "navigator", "path", "subdomain"],
+ caches: ["cookie"],
+ cookieMinutes: 525600, // 1 years
+});
diff --git a/front/src/providers/translations.native.tsx b/front/src/providers/translations.native.tsx
index 1c118e15..14bf84f5 100644
--- a/front/src/providers/translations.native.tsx
+++ b/front/src/providers/translations.native.tsx
@@ -3,11 +3,12 @@ import { type ReactNode, useMemo } from "react";
import { I18nextProvider } from "react-i18next";
import { setServerData } from "~/utils";
import { resources, supportedLanguages } from "./translations.compile";
+import { languageDetector } from "./translations-detector";
export const TranslationsProvider = ({ children }: { children: ReactNode }) => {
const val = useMemo(() => {
const i18n = i18next.createInstance();
- i18n.init({
+ i18n.use(languageDetector).init({
interpolation: {
escapeValue: false,
},
diff --git a/front/src/providers/translations.web.client.tsx b/front/src/providers/translations.web.client.tsx
index aaf129c9..7d37397b 100644
--- a/front/src/providers/translations.web.client.tsx
+++ b/front/src/providers/translations.web.client.tsx
@@ -4,24 +4,27 @@ import { type ReactNode, useMemo } from "react";
import { I18nextProvider } from "react-i18next";
import { getServerData } from "~/utils";
import { supportedLanguages } from "./translations.compile";
+import { languageDetector } from "./translations-detector";
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: supportedLanguages,
- // we don't need to cache resources since we always get a fresh one from ssr
- backend: {
- loadPath: "/translations/{{lng}}.json",
- },
- });
+ i18n
+ .use(HttpApi)
+ .use(languageDetector)
+ .init({
+ interpolation: {
+ escapeValue: false,
+ },
+ returnEmptyString: false,
+ fallbackLng: "en",
+ load: "currentOnly",
+ supportedLngs: supportedLanguages,
+ // 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;
}, []);
diff --git a/front/src/ui/settings/general.tsx b/front/src/ui/settings/general.tsx
new file mode 100644
index 00000000..f874073b
--- /dev/null
+++ b/front/src/ui/settings/general.tsx
@@ -0,0 +1,72 @@
+import Theme from "@material-symbols/svg-400/outlined/dark_mode.svg";
+import Language from "@material-symbols/svg-400/outlined/language.svg";
+import Android from "@material-symbols/svg-400/rounded/android.svg";
+import Public from "@material-symbols/svg-400/rounded/public.svg";
+import { useTranslation } from "react-i18next";
+import { Link, Select } from "~/primitives";
+import { supportedLanguages } from "~/providers/translations.compile";
+import { useLanguageName } from "~/track-utils";
+import { Preference, SettingsContainer } from "./base";
+
+export const GeneralSettings = () => {
+ const { t, i18n } = useTranslation();
+ // const theme = useUserTheme("auto");
+ const getLanguageName = useLanguageName();
+
+ return (
+
+ {/* */}
+ {/* */}
+
+
+
+ );
+};
+
+export const About = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/front/src/ui/settings/index.tsx b/front/src/ui/settings/index.tsx
index 3d8b4e11..3955ae3a 100644
--- a/front/src/ui/settings/index.tsx
+++ b/front/src/ui/settings/index.tsx
@@ -2,7 +2,7 @@ import { ScrollView } from "react-native";
import { ts } from "~/primitives";
import { useAccount } from "~/providers/account-context";
// import { AccountSettings } from "./account";
-// import { About, GeneralSettings } from "./general";
+import { About, GeneralSettings } from "./general";
// import { OidcSettings } from "./oidc";
// import { PlaybackSettings } from "./playback";
@@ -10,11 +10,11 @@ export const SettingsPage = () => {
const account = useAccount();
return (
- {/* */}
+
{/* {account && } */}
{/* {account && } */}
{/* {account && } */}
- {/* */}
+
);
};