mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add nextjs translations (via i18next)
This commit is contained in:
parent
fff73ed2b8
commit
5b78146db9
@ -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",
|
||||
|
@ -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",
|
||||
|
83
front/apps/web/src/i18n.tsx
Normal file
83
front/apps/web/src/i18n.tsx
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<AppProps> & {
|
||||
getInitialProps: (ctx: AppContext) => Promise<AppInitialProps>;
|
||||
},
|
||||
) => {
|
||||
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 (
|
||||
<I18nextProvider i18n={li18n}>
|
||||
<AppToTranslate {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
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;
|
||||
};
|
@ -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);
|
||||
|
@ -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);
|
||||
|
1
front/apps/web/src/styled-jsx.d.ts
vendored
1
front/apps/web/src/styled-jsx.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="styled-jsx" />
|
@ -15,8 +15,10 @@
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "*",
|
||||
"expo-linear-gradient": "*",
|
||||
"i18next": "*",
|
||||
"moti": "*",
|
||||
"react": "*",
|
||||
"react-i18next": "*",
|
||||
"react-native": "*",
|
||||
"react-native-reanimated": "*",
|
||||
"yoshiki": "*"
|
||||
|
@ -18,11 +18,11 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 (
|
||||
<Header
|
||||
|
49
front/translations/en.json
Normal file
49
front/translations/en.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"show": {
|
||||
"play": "Play",
|
||||
"trailer": "Play Trailer",
|
||||
"studio": "Studio",
|
||||
"genre": "Genres",
|
||||
"genre-none": "No genres",
|
||||
"staff": "Staff",
|
||||
"staff-none": "The staff is unknown",
|
||||
"noOverview": "No overview available",
|
||||
"episode-none": "There is no episodes in this season",
|
||||
"episodeNoMetadata": "No metadata available"
|
||||
},
|
||||
"browse": {
|
||||
"sortby": "Sort by {{key}}",
|
||||
"sortby-tt": "Sort by",
|
||||
"sortkey": {
|
||||
"name": "Name",
|
||||
"startAir": "Start air",
|
||||
"endAir": "End air"
|
||||
},
|
||||
"sortord": {
|
||||
"asc": "asc",
|
||||
"desc": "decs"
|
||||
},
|
||||
"switchToGrid": "Switch to grid view",
|
||||
"switchToList": "Switch to list view"
|
||||
},
|
||||
"misc": {
|
||||
"prev-page": "Previous page",
|
||||
"next-page": "Next page"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "Home",
|
||||
"login": "Login"
|
||||
},
|
||||
"player": {
|
||||
"back": "Back",
|
||||
"previous": "Previous episode",
|
||||
"next": "Next episode",
|
||||
"play": "Play",
|
||||
"pause": "Pause",
|
||||
"mute": "Toggle mute",
|
||||
"volume": "Volume",
|
||||
"subtitles": "Subtitles",
|
||||
"subtitle-none": "None",
|
||||
"fullscreen": "Fullscreen"
|
||||
}
|
||||
}
|
49
front/translations/fr.json
Normal file
49
front/translations/fr.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"show": {
|
||||
"play": "Lecture",
|
||||
"trailer": "Jouer le trailer",
|
||||
"studio": "Studio",
|
||||
"genre": "Genres",
|
||||
"genre-none": "Aucun genres",
|
||||
"staff": "Staff",
|
||||
"staff-none": "Aucun membre du staff connu",
|
||||
"noOverview": "Aucune description disponible",
|
||||
"episode-none": "Il n'y a pas d'épisodes dans cette saison",
|
||||
"episodeNoMetadata": "Aucune metadonnée disponible"
|
||||
},
|
||||
"browse": {
|
||||
"sortby": "Trier par {{key}}",
|
||||
"sortby-tt": "Trier par",
|
||||
"sortkey": {
|
||||
"name": "Nom",
|
||||
"startAir": "Date de sortie",
|
||||
"endAir": "Date de fin de sortie"
|
||||
},
|
||||
"sortord": {
|
||||
"asc": "asc",
|
||||
"desc": "decs"
|
||||
},
|
||||
"switchToGrid": "Passer en vue grille",
|
||||
"switchToList": "Passer en vue liste"
|
||||
},
|
||||
"misc": {
|
||||
"prev-page": "Page précédente",
|
||||
"next-page": "Page suivante"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "Accueil",
|
||||
"login": "Connexion"
|
||||
},
|
||||
"player": {
|
||||
"back": "Retour",
|
||||
"previous": "Episode précédent",
|
||||
"next": "Episode suivant",
|
||||
"play": "Jouer",
|
||||
"pause": "Pause",
|
||||
"mute": "Muet",
|
||||
"volume": "Volume",
|
||||
"subtitles": "Sous titres",
|
||||
"subtitle-none": "Aucun",
|
||||
"fullscreen": "Plein-écran"
|
||||
}
|
||||
}
|
@ -1444,7 +1444,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
|
||||
version: 7.20.6
|
||||
resolution: "@babel/runtime@npm:7.20.6"
|
||||
dependencies:
|
||||
@ -2328,8 +2328,10 @@ __metadata:
|
||||
peerDependencies:
|
||||
"@tanstack/react-query": "*"
|
||||
expo-linear-gradient: "*"
|
||||
i18next: "*"
|
||||
moti: "*"
|
||||
react: "*"
|
||||
react-i18next: "*"
|
||||
react-native: "*"
|
||||
react-native-reanimated: "*"
|
||||
yoshiki: "*"
|
||||
@ -7371,6 +7373,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-parse-stringify@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "html-parse-stringify@npm:3.0.1"
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
checksum: 334fdebd4b5c355dba8e95284cead6f62bf865a2359da2759b039db58c805646350016d2017875718bc3c4b9bf81a0d11be5ee0cf4774a3a5a7b97cde21cfd67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-cache-semantics@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "http-cache-semantics@npm:4.1.0"
|
||||
@ -7441,6 +7452,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"i18next@npm:^22.0.6":
|
||||
version: 22.0.6
|
||||
resolution: "i18next@npm:22.0.6"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.17.2
|
||||
checksum: b9d20b8d6cdd16c38def15d4f1c5906418926412eafa7d582fbd8395618deb177e73bdc709559480de7e1a3281287b69665d6f7c2af8df791f10ea1dfd79cb79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:0.4.24":
|
||||
version: 0.4.24
|
||||
resolution: "iconv-lite@npm:0.4.24"
|
||||
@ -9607,9 +9627,11 @@ __metadata:
|
||||
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
|
||||
@ -10839,6 +10861,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-i18next@npm:^12.0.0":
|
||||
version: 12.0.0
|
||||
resolution: "react-i18next@npm:12.0.0"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.14.5
|
||||
html-parse-stringify: ^3.0.1
|
||||
peerDependencies:
|
||||
i18next: ">= 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user