Add nextjs translations (via i18next)

This commit is contained in:
Zoe Roux 2022-12-05 23:34:20 +09:00
parent fff73ed2b8
commit 5b78146db9
11 changed files with 247 additions and 22 deletions

View File

@ -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",

View File

@ -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",

View 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;
};

View File

@ -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);

View File

@ -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);

View File

@ -1 +0,0 @@
/// <reference types="styled-jsx" />

View File

@ -15,8 +15,10 @@
"peerDependencies": {
"@tanstack/react-query": "*",
"expo-linear-gradient": "*",
"i18next": "*",
"moti": "*",
"react": "*",
"react-i18next": "*",
"react-native": "*",
"react-native-reanimated": "*",
"yoshiki": "*"

View File

@ -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

View 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"
}
}

View 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"
}
}

View File

@ -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