diff --git a/front/apps/mobile/package.json b/front/apps/mobile/package.json index 2e04546c..29511949 100644 --- a/front/apps/mobile/package.json +++ b/front/apps/mobile/package.json @@ -17,9 +17,11 @@ "expo-linking": "~3.2.3", "expo-router": "^0.0.36", "expo-status-bar": "~1.4.2", + "i18next": "^22.0.6", "moti": "^0.21.0", "react": "18.1.0", "react-dom": "18.1.0", + "react-i18next": "^12.0.0", "react-native": "0.70.5", "react-native-reanimated": "~2.12.0", "react-native-safe-area-context": "4.4.1", diff --git a/front/apps/web/package.json b/front/apps/web/package.json index 3d02cd4d..aa8044fb 100644 --- a/front/apps/web/package.json +++ b/front/apps/web/package.json @@ -25,6 +25,7 @@ "csstype": "^3.1.1", "expo-linear-gradient": "^12.0.1", "hls.js": "^1.2.8", + "i18next": "^22.0.6", "jotai": "^1.10.0", "moti": "^0.21.0", "next": "13.0.5", @@ -34,6 +35,7 @@ "raf": "^3.4.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-i18next": "^12.0.0", "react-infinite-scroll-component": "^6.1.0", "react-native-reanimated": "^2.13.0", "react-native-web": "^0.18.10", diff --git a/front/apps/web/src/i18n.tsx b/front/apps/web/src/i18n.tsx new file mode 100644 index 00000000..f9d49c81 --- /dev/null +++ b/front/apps/web/src/i18n.tsx @@ -0,0 +1,83 @@ +/* + * 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 { ComponentType, useMemo } from "react"; +import i18next, { InitOptions } from "i18next"; +import { I18nextProvider } from "react-i18next"; +import { AppContext, AppInitialProps, type AppProps } from "next/app"; + +import en from "../../../translations/en.json"; +import fr from "../../../translations/fr.json"; + +export const withTranslations = ( + AppToTranslate: ComponentType & { + getInitialProps: (ctx: AppContext) => Promise; + }, +) => { + const i18n = i18next.createInstance(); + const commonOptions: InitOptions = { + debug: true, + interpolation: { + escapeValue: false, + }, + }; + + const AppWithTranslations = (props: AppProps) => { + const li18n = useMemo( + () => + typeof window === "undefined" + ? i18n + : (i18next.init({ + ...commonOptions, + lng: props.pageProps.__lang, + resources: props.pageProps.__resources, + }), + i18next), + [props.pageProps.__lang, props.pageProps.__resources], + ); + + return ( + + + + ); + }; + AppWithTranslations.getInitialProps = async (ctx: AppContext) => { + const props: AppInitialProps = await AppToTranslate.getInitialProps(ctx); + const lng = ctx.router.locale || ctx.router.defaultLocale || "en"; + // TODO: use a backend to fetch only the needed translations. + // TODO: use a different backend on the client and fetch needed translations. + const resources = { + en: { translation: en }, + fr: { translation: fr }, + }; + await i18n.init({ + ...commonOptions, + lng, + fallbackLng: ctx.router.defaultLocale || "en", + resources, + }); + props.pageProps.__lang = lng; + props.pageProps.__resources = resources; + return props; + }; + + return AppWithTranslations; +}; diff --git a/front/apps/web/src/pages/_app.tsx b/front/apps/web/src/pages/_app.tsx index dff31e9a..f9196fda 100755 --- a/front/apps/web/src/pages/_app.tsx +++ b/front/apps/web/src/pages/_app.tsx @@ -21,15 +21,15 @@ import "../polyfill"; import { ReactNode, useState } from "react"; -import appWithI18n from "next-translate/appWithI18n"; -import { useTheme, useMobileHover } from "yoshiki/web"; -import { createTheme, ThemeProvider as MTheme } from "@mui/material"; import NextApp, { AppContext, type AppProps } from "next/app"; +import { createTheme, ThemeProvider as MTheme } from "@mui/material"; import { Hydrate, QueryClientProvider } from "@tanstack/react-query"; +import { ThemeSelector as KThemeSelector, WebTooltip } from "@kyoo/primitives"; import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "@kyoo/models"; +import { useTheme, useMobileHover } from "yoshiki/web"; import superjson from "superjson"; import Head from "next/head"; -import { ThemeSelector as KThemeSelector, WebTooltip } from "@kyoo/primitives"; +import { withTranslations } from "../i18n"; const ThemeSelector = ({ children }: { children?: ReactNode | ReactNode[] }) => { // TODO: Handle user selected mode (light, dark, auto) @@ -106,15 +106,5 @@ App.getInitialProps = async (ctx: AppContext) => { return { pageProps: superjson.serialize(appProps.pageProps) }; }; -// The as any is needed since appWithI18n as wrong type hints -export default appWithI18n(App as any, { - skipInitialProps: false, - locales: ["en", "fr"], - defaultLocale: "en", - loader: false, - pages: { - "*": ["common", "browse", "player"], - }, - loadLocaleFrom: (locale, namespace) => - import(`../../locales/${locale}/${namespace}`).then((m) => m.default), -}); + +export default withTranslations(App); diff --git a/front/apps/web/src/pages/browse/index.tsx b/front/apps/web/src/pages/browse/index.tsx index 9178c46d..64fc8e46 100644 --- a/front/apps/web/src/pages/browse/index.tsx +++ b/front/apps/web/src/pages/browse/index.tsx @@ -433,7 +433,7 @@ BrowsePage.getLayout = (page) => { BrowsePage.getFetchUrls = ({ slug, sortBy }) => [ query(slug, sortBy?.split("-")[0] as SortBy, sortBy?.split("-")[1] as SortOrd), - /* Navbar.query(), */ + Navbar.query(), ]; export default withRoute(BrowsePage); diff --git a/front/apps/web/src/styled-jsx.d.ts b/front/apps/web/src/styled-jsx.d.ts deleted file mode 100644 index 58518fe7..00000000 --- a/front/apps/web/src/styled-jsx.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/front/packages/ui/package.json b/front/packages/ui/package.json index 9561b9ef..d8a4ea5b 100644 --- a/front/packages/ui/package.json +++ b/front/packages/ui/package.json @@ -15,8 +15,10 @@ "peerDependencies": { "@tanstack/react-query": "*", "expo-linear-gradient": "*", + "i18next": "*", "moti": "*", "react": "*", + "react-i18next": "*", "react-native": "*", "react-native-reanimated": "*", "yoshiki": "*" diff --git a/front/packages/ui/src/navbar/index.tsx b/front/packages/ui/src/navbar/index.tsx index 09b65589..42df77f1 100644 --- a/front/packages/ui/src/navbar/index.tsx +++ b/front/packages/ui/src/navbar/index.tsx @@ -18,11 +18,11 @@ * along with Kyoo. If not, see . */ -import useTranslation from "next-translate/useTranslation"; import { Library, LibraryP, Page, Paged, QueryIdentifier } from "@kyoo/models"; -import { rem, useYoshiki } from "yoshiki/native"; import { IconButton, Header, Avatar, A, Skeleton, tooltip, ts } from "@kyoo/primitives"; import { View } from "react-native"; +import { useTranslation } from 'react-i18next'; +import { rem, useYoshiki } from "yoshiki/native"; import { Fetch } from "../fetch"; import { KyooLongLogo } from "./icon"; @@ -30,7 +30,7 @@ export const NavbarTitle = KyooLongLogo; export const Navbar = () => { const { css } = useYoshiki(); - const { t } = useTranslation("common"); + const { t } = useTranslation(); return (
= 19.0.0" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: f523d7ec5dcb7f5fd36efc9385639d01160071f4dadbbc5a3f1daa64b0f332f709347bdd3bc68f5aefbd72c8742233aa715b308b1ca87179ad501acc19c72068 + languageName: node + linkType: hard + "react-infinite-scroll-component@npm:^6.1.0": version: 6.1.0 resolution: "react-infinite-scroll-component@npm:6.1.0" @@ -13116,6 +13156,13 @@ __metadata: languageName: node linkType: hard +"void-elements@npm:3.1.0": + version: 3.1.0 + resolution: "void-elements@npm:3.1.0" + checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f + languageName: node + linkType: hard + "walker@npm:^1.0.7": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -13175,6 +13222,7 @@ __metadata: eslint-config-next: 13.0.5 expo-linear-gradient: ^12.0.1 hls.js: ^1.2.8 + i18next: ^22.0.6 jotai: ^1.10.0 moti: ^0.21.0 next: 13.0.5 @@ -13184,6 +13232,7 @@ __metadata: raf: ^3.4.1 react: 18.2.0 react-dom: 18.2.0 + react-i18next: ^12.0.0 react-infinite-scroll-component: ^6.1.0 react-native-reanimated: ^2.13.0 react-native-web: ^0.18.10