From 61708857afb0052e326cbe6cce8f431ccfbd4f93 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 8 Nov 2025 15:36:15 +0100 Subject: [PATCH 1/9] Rework useMutation optimistic updates & start settings page --- front/packages/ui/src/settings/index.tsx | 44 ------------ front/src/app/(public)/settings.tsx | 3 + front/src/query/query.tsx | 37 +++++++--- .../ui/src => src/ui}/settings/base.tsx | 70 ++++++------------- front/src/ui/settings/index.tsx | 20 ++++++ 5 files changed, 71 insertions(+), 103 deletions(-) delete mode 100644 front/packages/ui/src/settings/index.tsx create mode 100644 front/src/app/(public)/settings.tsx rename front/{packages/ui/src => src/ui}/settings/base.tsx (54%) create mode 100644 front/src/ui/settings/index.tsx diff --git a/front/packages/ui/src/settings/index.tsx b/front/packages/ui/src/settings/index.tsx deleted file mode 100644 index de60c769..00000000 --- a/front/packages/ui/src/settings/index.tsx +++ /dev/null @@ -1,44 +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 { type QueryPage, useAccount } from "@kyoo/models"; -import { ts } from "@kyoo/primitives"; -import { ScrollView } from "react-native"; -import { DefaultLayout } from "../layout"; -import { AccountSettings } from "./account"; -import { About, GeneralSettings } from "./general"; -import { OidcSettings } from "./oidc"; -import { PlaybackSettings } from "./playback"; - -export const SettingsPage: QueryPage = () => { - const account = useAccount(); - return ( - - - {account && } - {account && } - {account && } - - - ); -}; - -SettingsPage.getLayout = DefaultLayout; -SettingsPage.getFetchUrls = () => [OidcSettings.query()]; diff --git a/front/src/app/(public)/settings.tsx b/front/src/app/(public)/settings.tsx new file mode 100644 index 00000000..3cc7b47a --- /dev/null +++ b/front/src/app/(public)/settings.tsx @@ -0,0 +1,3 @@ +import { SettingsPage } from "~/ui/settings"; + +export default SettingsPage; diff --git a/front/src/query/query.tsx b/front/src/query/query.tsx index 55526708..ed5d1f2c 100644 --- a/front/src/query/query.tsx +++ b/front/src/query/query.tsx @@ -260,7 +260,7 @@ export const prefetch = async (...queries: QueryIdentifier[]) => { }; type MutationParams = { - method?: "POST" | "PUT" | "DELETE"; + method?: "POST" | "PUT" | "PATCH" | "DELETE"; path?: string[]; params?: { [query: string]: boolean | number | string | string[] | undefined; @@ -268,12 +268,14 @@ type MutationParams = { body?: object; }; -export const useMutation = ({ +export const useMutation = ({ compute, invalidate, + optimistic, ...queryParams }: MutationParams & { compute?: (param: T) => MutationParams; + optimistic?: ((param: T) => QueryRet); invalidate: string[] | null; }) => { const { apiUrl, authToken } = useContext(AccountContext); @@ -293,14 +295,29 @@ export const useMutation = ({ parser: null, }); }, - onSuccess: invalidate - ? async () => - await queryClient.invalidateQueries({ - queryKey: toQueryKey({ apiUrl, path: invalidate }), - }) - : undefined, - // TODO: Do something - // onError: () => {} + ...(invalidate && optimistic + ? { + onMutate: async (params) => { + const next = optimistic(params); + await queryClient.cancelQueries({ queryKey: invalidate }); + const previous = queryClient.getQueryData(invalidate); + queryClient.setQueryData(invalidate, next); + return { previous, next }; + }, + onError: (_, __, context) => { + queryClient.setQueryData(invalidate, context!.previous); + }, + } + : {}), + ...(invalidate + ? { + onSettled: async () => { + await queryClient.invalidateQueries({ + queryKey: toQueryKey({ apiUrl, path: invalidate }), + }); + }, + } + : {}), }); return mutation; }; diff --git a/front/packages/ui/src/settings/base.tsx b/front/src/ui/settings/base.tsx similarity index 54% rename from front/packages/ui/src/settings/base.tsx rename to front/src/ui/settings/base.tsx index 3254b375..d22ab1c6 100644 --- a/front/packages/ui/src/settings/base.tsx +++ b/front/src/ui/settings/base.tsx @@ -1,24 +1,7 @@ -/* - * 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 { type User, queryFn, useAccount } from "@kyoo/models"; +import { Children, type ReactElement, type ReactNode } from "react"; +import { type Falsy, View } from "react-native"; +import { percent, px, rem, useYoshiki } from "yoshiki/native"; +import type { User } from "~/models"; import { Container, H1, @@ -27,13 +10,10 @@ import { P, SubP, SwitchVariant, - imageBorderRadius, ts, -} from "@kyoo/primitives"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { Children, type ReactElement, type ReactNode } from "react"; -import { type Falsy, View } from "react-native"; -import { percent, px, rem, useYoshiki } from "yoshiki/native"; +} from "~/primitives"; +import { useAccount } from "~/providers/account-context"; +import { useMutation } from "~/query"; export const Preference = ({ customIcon, @@ -75,7 +55,7 @@ export const Preference = ({ > {customIcon ?? } -

{label}

+

{label}

{description}
@@ -118,7 +98,7 @@ export const SettingsContainer = ({ theme.background, - borderRadius: px(imageBorderRadius), + borderRadius: px(6), })} > {Children.map(children, (x, i) => ( @@ -135,28 +115,20 @@ export const SettingsContainer = ({ ); }; -export const useSetting = (setting: Setting) => { +export const useSetting = ( + setting: Setting, +) => { const account = useAccount(); - - const queryClient = useQueryClient(); const { mutateAsync } = useMutation({ - mutationFn: async (update: Partial) => - await queryFn({ - path: ["auth", "me"], - method: "PATCH", - body: { settings: { ...account!.settings, ...update } }, - }), - onMutate: async (newSettings) => { - const next = { ...account!, settings: { ...account!.settings, ...newSettings } }; - await queryClient.cancelQueries({ queryKey: ["auth", "me"] }); - const previous = queryClient.getQueryData(["auth", "me"]); - queryClient.setQueryData(["auth", "me"], next); - return { previous, next }; - }, - onError: (_, __, context) => { - queryClient.setQueryData(["auth", "me"], context!.previous); - }, - onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["auth", "me"] }), + method: "PATCH", + path: ["auth", "me"], + compute: (update: Partial) => ({ + body: { settings: { ...account!.settings, ...update } }, + }), + optimistic: (update) => ({ + body: { ...account, settings: { ...account!.settings, ...update } }, + }), + invalidate: ["auth", "me"], }); if (!account) return null; diff --git a/front/src/ui/settings/index.tsx b/front/src/ui/settings/index.tsx new file mode 100644 index 00000000..3d8b4e11 --- /dev/null +++ b/front/src/ui/settings/index.tsx @@ -0,0 +1,20 @@ +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 { OidcSettings } from "./oidc"; +// import { PlaybackSettings } from "./playback"; + +export const SettingsPage = () => { + const account = useAccount(); + return ( + + {/* */} + {/* {account && } */} + {/* {account && } */} + {/* {account && } */} + {/* */} + + ); +}; From 951ae955edd96b600b3a4ac5c2f9d5816d2a01d1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 8 Nov 2025 17:45:09 +0100 Subject: [PATCH 2/9] Use a postinstall script to generate translation list --- front/Dockerfile | 3 +- front/Dockerfile.dev | 1 + front/biome.json | 5 +- .../translations/{pt_br.json => pt-BR.json} | 0 front/scripts/postinstall.ts | 59 +++++++++++-- front/src/providers/translations.compile.ts | 85 +++++++++++++++---- 6 files changed, 125 insertions(+), 28 deletions(-) rename front/public/translations/{pt_br.json => pt-BR.json} (100%) diff --git a/front/Dockerfile b/front/Dockerfile index 5db071c6..b3f2ef61 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -5,8 +5,9 @@ WORKDIR /app RUN apt update && apt install -y nodejs && rm /usr/local/bun-node-fallback-bin/node ENV NODE_ENV=production -COPY package.json bun.lock scripts . +COPY package.json bun.lock . COPY scripts scripts +COPY public public RUN bun install --production COPY . . diff --git a/front/Dockerfile.dev b/front/Dockerfile.dev index 2bfbec5a..b32d1539 100644 --- a/front/Dockerfile.dev +++ b/front/Dockerfile.dev @@ -3,6 +3,7 @@ WORKDIR /app COPY package.json bun.lock . COPY scripts scripts +COPY public public RUN bun install --frozen-lockfile COPY . . diff --git a/front/biome.json b/front/biome.json index 8c2c8f47..ca911e9e 100644 --- a/front/biome.json +++ b/front/biome.json @@ -1,6 +1,9 @@ { "extends": "//", "files": { - "includes": ["src/**"] + "includes": [ + "src/**", + "scripts/**" + ] } } diff --git a/front/public/translations/pt_br.json b/front/public/translations/pt-BR.json similarity index 100% rename from front/public/translations/pt_br.json rename to front/public/translations/pt-BR.json diff --git a/front/scripts/postinstall.ts b/front/scripts/postinstall.ts index 2ec18601..0f7a2a0c 100644 --- a/front/scripts/postinstall.ts +++ b/front/scripts/postinstall.ts @@ -1,12 +1,55 @@ -import { readdir , mkdir } from 'node:fs/promises'; +import { mkdir, readdir } from "node:fs/promises"; -const srcDir = new URL("../node_modules/jassub/dist/", import.meta.url); -const destDir = new URL("../public/jassub/", import.meta.url); +async function jassub() { + const srcDir = new URL("../node_modules/jassub/dist/", import.meta.url); + const destDir = new URL("../public/jassub/", import.meta.url); -await mkdir(destDir, { recursive: true }); + await mkdir(destDir, { recursive: true }); -const files = await readdir(srcDir); -for (const file of files) { - const src = await Bun.file(new URL(file, srcDir)).arrayBuffer(); - await Bun.write(new URL(file, destDir), src); + const files = await readdir(srcDir); + for (const file of files) { + const src = await Bun.file(new URL(file, srcDir)).arrayBuffer(); + await Bun.write(new URL(file, destDir), src); + } } + +async function translations() { + const srcDir = new URL("../public/translations/", import.meta.url); + const dest = new URL( + "../src/providers/translations.compile.ts", + import.meta.url, + ); + + const translations = (await readdir(srcDir)) + .map((x) => ({ + file: x, + lang: x.replace(".json", ""), + var: x.replace(".json", "").replace("-", "_"), + })) + .map((x) => ({ + ...x, + quotedLang: x.lang.includes("-") ? `"${x.lang}"` : x.lang, + })) + .sort((a, b) => a.lang.localeCompare(b.lang)); + await Bun.write( + dest, + `// this file is auto-generated via a postinstall script. + +${translations + .map((x) => `import ${x.var} from "../../public/translations/${x.file}";`) + .join("\n")} + +export const resources = { + ${translations + .map((x) => `${x.quotedLang}: { translation: ${x.var} },`) + .join("\n\t")} +}; + +export const supportedLanguages = [ + ${translations.map((x) => `"${x.lang}",`).join("\n\t")} +];`, + ); +} + +await jassub(); +await translations(); diff --git a/front/src/providers/translations.compile.ts b/front/src/providers/translations.compile.ts index 55b100f6..1296173f 100644 --- a/front/src/providers/translations.compile.ts +++ b/front/src/providers/translations.compile.ts @@ -1,22 +1,71 @@ -// this file is run at compile time thanks to a vite plugin - -// import { readFile, readdir } from "node:fs/promises"; -// import type { Resource } from "i18next"; - -// const translationDir = new URL("../../public/translations/", import.meta.url); -// const langs = await readdir(translationDir); - -// export const resources: Resource = Object.fromEntries( -// await Promise.all( -// langs.map(async (x) => [ -// x.replace(".json", ""), -// { translation: JSON.parse(await readFile(new URL(x, translationDir), "utf8")) }, -// ]), -// ), -// ); +// this file is auto-generated via a postinstall script. +import am from "../../public/translations/am.json"; +import ar from "../../public/translations/ar.json"; +import de from "../../public/translations/de.json"; import en from "../../public/translations/en.json"; +import es from "../../public/translations/es.json"; +import fr from "../../public/translations/fr.json"; +import gl from "../../public/translations/gl.json"; +import is from "../../public/translations/is.json"; +import it from "../../public/translations/it.json"; +import ko from "../../public/translations/ko.json"; +import ml from "../../public/translations/ml.json"; +import nl from "../../public/translations/nl.json"; +import pl from "../../public/translations/pl.json"; +import pt from "../../public/translations/pt.json"; +import pt_BR from "../../public/translations/pt-BR.json"; +import ro from "../../public/translations/ro.json"; +import ru from "../../public/translations/ru.json"; +import ta from "../../public/translations/ta.json"; +import tr from "../../public/translations/tr.json"; +import uk from "../../public/translations/uk.json"; +import zh from "../../public/translations/zh.json"; -export const resources = { en }; +export const resources = { + am: { translation: am }, + ar: { translation: ar }, + de: { translation: de }, + en: { translation: en }, + es: { translation: es }, + fr: { translation: fr }, + gl: { translation: gl }, + is: { translation: is }, + it: { translation: it }, + ko: { translation: ko }, + ml: { translation: ml }, + nl: { translation: nl }, + pl: { translation: pl }, + pt: { translation: pt }, + "pt-BR": { translation: pt_BR }, + ro: { translation: ro }, + ru: { translation: ru }, + ta: { translation: ta }, + tr: { translation: tr }, + uk: { translation: uk }, + zh: { translation: zh }, +}; -export const supportedLanguages = Object.keys(resources); +export const supportedLanguages = [ + "am", + "ar", + "de", + "en", + "es", + "fr", + "gl", + "is", + "it", + "ko", + "ml", + "nl", + "pl", + "pt", + "pt-BR", + "ro", + "ru", + "ta", + "tr", + "uk", + "zh", +]; \ No newline at end of file From 079cc6b4f974db3d7bcd5c659191519311a578e4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 8 Nov 2025 17:55:23 +0100 Subject: [PATCH 3/9] Fix translations keys --- front/public/translations/am.json | 49 ++++++++++++++-------------- front/public/translations/ar.json | 49 ++++++++++++++-------------- front/public/translations/de.json | 49 ++++++++++++++-------------- front/public/translations/en.json | 49 ++++++++++++++-------------- front/public/translations/es.json | 49 ++++++++++++++-------------- front/public/translations/fr.json | 49 ++++++++++++++-------------- front/public/translations/gl.json | 49 ++++++++++++++-------------- front/public/translations/is.json | 49 ++++++++++++++-------------- front/public/translations/it.json | 49 ++++++++++++++-------------- front/public/translations/ko.json | 49 ++++++++++++++-------------- front/public/translations/ml.json | 49 ++++++++++++++-------------- front/public/translations/nl.json | 49 ++++++++++++++-------------- front/public/translations/pl.json | 49 ++++++++++++++-------------- front/public/translations/pt-BR.json | 49 ++++++++++++++-------------- front/public/translations/pt.json | 49 ++++++++++++++-------------- front/public/translations/ro.json | 49 ++++++++++++++-------------- front/public/translations/ru.json | 49 ++++++++++++++-------------- front/public/translations/ta.json | 49 ++++++++++++++-------------- front/public/translations/tr.json | 49 ++++++++++++++-------------- front/public/translations/uk.json | 49 ++++++++++++++-------------- front/public/translations/zh.json | 49 ++++++++++++++-------------- 21 files changed, 504 insertions(+), 525 deletions(-) diff --git a/front/public/translations/am.json b/front/public/translations/am.json index ac9bbcfd..1803cc0c 100644 --- a/front/public/translations/am.json +++ b/front/public/translations/am.json @@ -16,7 +16,7 @@ "show": { "play": "ያጫውቱ", "trailer": "ትሬለሩን አጫውት", - "studio": "ስቱዲዮ", + "studios": "ስቱዲዮ", "genre": "ዘውግ", "genre-none": "ዘውግ የለውም", "staff": "ስታፍ", @@ -70,30 +70,29 @@ "switchToList": "" }, "genres": { - "Action": "", - "Adventure": "", - "Animation": "", - "Comedy": "", - "Crime": "", - "Documentary": "", - "Drama": "", - "Family": "", - "Fantasy": "", - "History": "", - "Horror": "", - "Music": "", - "Mystery": "", - "Romance": "", - "ScienceFiction": "", - "Thriller": "", - "War": "", - "Western": "", - "Kids": "", - "News": "", - "Reality": "", - "Soap": "", - "Talk": "", - "Politics": "" + "action": "", + "adventure": "", + "animation": "", + "comedy": "", + "crime": "", + "documentary": "", + "drama": "", + "family": "", + "fantasy": "", + "history": "", + "horror": "", + "music": "", + "mystery": "", + "romance": "", + "science-fiction": "", + "thriller": "", + "war": "", + "western": "", + "kids": "", + "reality": "", + "soap": "", + "talk": "", + "politics": "" }, "misc": { "settings": "", diff --git a/front/public/translations/ar.json b/front/public/translations/ar.json index 62e3f134..76901085 100644 --- a/front/public/translations/ar.json +++ b/front/public/translations/ar.json @@ -16,7 +16,7 @@ "show": { "play": "تشغيل", "trailer": "تشغيل المقطع الدعائي", - "studio": "استوديو", + "studios": "استوديو", "genre": "الأنواع", "genre-none": "لا توجد أنواع", "staff": "الطاقم", @@ -70,30 +70,29 @@ "switchToList": "التبديل إلى عرض القائمة" }, "genres": { - "Action": "أكشن", - "Adventure": "مغامرات", - "Animation": "أنميشن", - "Comedy": "كوميدي", - "Crime": "جريمة", - "Documentary": "وثائقي", - "Drama": "دراما", - "Family": "عائلي", - "Fantasy": "خيال", - "History": "تاريخي", - "Horror": "رعب", - "Music": "موسيقى", - "Mystery": "غموض", - "Romance": "رومانسي", - "ScienceFiction": "خيال علمي", - "Thriller": "إثارة", - "War": "حرب", - "Western": "غربي", - "Kids": "أطفال", - "News": "أخبار", - "Reality": "واقع", - "Soap": "دراما طويلة", - "Talk": "حوار", - "Politics": "سياسة" + "action": "أكشن", + "adventure": "مغامرات", + "animation": "أنميشن", + "comedy": "كوميدي", + "crime": "جريمة", + "documentary": "وثائقي", + "drama": "دراما", + "family": "عائلي", + "fantasy": "خيال", + "history": "تاريخي", + "horror": "رعب", + "music": "موسيقى", + "mystery": "غموض", + "romance": "رومانسي", + "science-fiction": "خيال علمي", + "thriller": "إثارة", + "war": "حرب", + "western": "غربي", + "kids": "أطفال", + "reality": "واقع", + "soap": "دراما طويلة", + "talk": "حوار", + "politics": "سياسة" }, "misc": { "settings": "إعدادات", diff --git a/front/public/translations/de.json b/front/public/translations/de.json index beb6191b..a56bc5d6 100644 --- a/front/public/translations/de.json +++ b/front/public/translations/de.json @@ -16,7 +16,7 @@ "show": { "play": "Abspielen", "trailer": "Trailer abspielen", - "studio": "Studio", + "studios": "Studio", "genre": "Genres", "genre-none": "Keine Genres", "staff": "Besetzung", @@ -259,29 +259,28 @@ } }, "genres": { - "Family": "Familienfilm", - "Animation": "Animation", - "Comedy": "Komödie", - "Crime": "Krimi", - "Documentary": "Dokumentation", - "Drama": "Drama", - "Fantasy": "Fantasy", - "Horror": "Horror", - "Mystery": "Mystery", - "Romance": "Liebesfilm", - "ScienceFiction": "Science-Fiction", - "Thriller": "Thriller", - "War": "Kriegsfilm", - "Western": "Western", - "Kids": "Kinderfilm", - "News": "Neu", - "Reality": "Reality-TV", - "Soap": "Soap", - "Talk": "Talkshow", - "Politics": "Politik", - "Adventure": "Abenteuer", - "History": "Geschichte", - "Music": "Musikfilm", - "Action": "Action" + "family": "Familienfilm", + "animation": "Animation", + "comedy": "Komödie", + "crime": "Krimi", + "documentary": "Dokumentation", + "drama": "Drama", + "fantasy": "Fantasy", + "horror": "Horror", + "mystery": "Mystery", + "romance": "Liebesfilm", + "science-fiction": "Science-Fiction", + "thriller": "Thriller", + "war": "Kriegsfilm", + "western": "Western", + "kids": "Kinderfilm", + "reality": "Reality-TV", + "soap": "Soap", + "talk": "Talkshow", + "politics": "Politik", + "adventure": "Abenteuer", + "history": "Geschichte", + "music": "Musikfilm", + "action": "Action" } } diff --git a/front/public/translations/en.json b/front/public/translations/en.json index 0551af65..e2a5fbac 100644 --- a/front/public/translations/en.json +++ b/front/public/translations/en.json @@ -16,7 +16,7 @@ "show": { "play": "Play", "trailer": "Play Trailer", - "studio": "Studio", + "studios": "Studios", "genre": "Genres", "genre-none": "No genres", "staff": "Staff", @@ -71,30 +71,29 @@ "switchToList": "Switch to list view" }, "genres": { - "Action": "Action", - "Adventure": "Adventure", - "Animation": "Animation", - "Comedy": "Comedy", - "Crime": "Crime", - "Documentary": "Documentary", - "Drama": "Drama", - "Family": "Family", - "Fantasy": "Fantasy", - "History": "History", - "Horror": "Horror", - "Music": "Music", - "Mystery": "Mystery", - "Romance": "Romance", - "ScienceFiction": "Science Fiction", - "Thriller": "Thriller", - "War": "War", - "Western": "Western", - "Kids": "Kids", - "News": "News", - "Reality": "Reality", - "Soap": "Soap", - "Talk": "Talk", - "Politics": "Politics" + "action": "Action", + "adventure": "Adventure", + "animation": "Animation", + "comedy": "Comedy", + "crime": "Crime", + "documentary": "Documentary", + "drama": "Drama", + "family": "Family", + "fantasy": "Fantasy", + "history": "History", + "horror": "Horror", + "music": "Music", + "mystery": "Mystery", + "romance": "Romance", + "science-fiction": "Science Fiction", + "thriller": "Thriller", + "war": "War", + "western": "Western", + "kids": "Kids", + "reality": "Reality", + "politics": "Politics", + "soap": "Soap", + "talk": "Talk" }, "misc": { "settings": "Settings", diff --git a/front/public/translations/es.json b/front/public/translations/es.json index ea308211..4e1a7091 100644 --- a/front/public/translations/es.json +++ b/front/public/translations/es.json @@ -16,7 +16,7 @@ "show": { "play": "Reproducir", "trailer": "Ver el tráiler", - "studio": "Estudio", + "studios": "Estudio", "genre": "Géneros", "genre-none": "Sin géneros", "staff": "Equipo", @@ -70,30 +70,29 @@ "switchToList": "Cambiar a vista de lista" }, "genres": { - "Action": "Acción", - "Adventure": "Aventura", - "Animation": "Animación", - "Comedy": "Comedia", - "Crime": "Crimen", - "Documentary": "Documental", - "Drama": "Drama", - "Family": "Familia", - "Fantasy": "Fantasía", - "History": "Historia", - "Horror": "Horror", - "Music": "Musica", - "Mystery": "Misterio", - "Romance": "Romance", - "ScienceFiction": "Ciencia ficción", - "Thriller": "Suspenso", - "War": "Bélica", - "Western": "Del oeste", - "Kids": "Niños", - "News": "Noticias", - "Reality": "Realidad", - "Soap": "Novela", - "Talk": "Entrevista", - "Politics": "Política" + "action": "Acción", + "adventure": "Aventura", + "animation": "Animación", + "comedy": "Comedia", + "crime": "Crimen", + "documentary": "Documental", + "drama": "Drama", + "family": "Familia", + "fantasy": "Fantasía", + "history": "Historia", + "horror": "Horror", + "music": "Musica", + "mystery": "Misterio", + "romance": "Romance", + "science-fiction": "Ciencia ficción", + "thriller": "Suspenso", + "war": "Bélica", + "western": "Del oeste", + "kids": "Niños", + "reality": "Realidad", + "soap": "Novela", + "talk": "Entrevista", + "politics": "Política" }, "misc": { "settings": "Ajustes", diff --git a/front/public/translations/fr.json b/front/public/translations/fr.json index 6d4bdfbc..57471008 100644 --- a/front/public/translations/fr.json +++ b/front/public/translations/fr.json @@ -16,7 +16,7 @@ "show": { "play": "Lecture", "trailer": "Lire la bande annonce", - "studio": "Studio", + "studios": "Studio", "genre": "Genres", "genre-none": "Aucun genres", "staff": "Équipe", @@ -259,29 +259,28 @@ } }, "genres": { - "Action": "Action", - "Adventure": "Aventure", - "Comedy": "Comédie", - "Documentary": "Documentaire", - "Drama": "Drame", - "Family": "Famille", - "Fantasy": "Fantastique", - "History": "Histoire", - "Crime": "Scène de crime", - "Horror": "Horreur", - "Music": "Musique", - "Mystery": "Mystère", - "Romance": "Romance", - "ScienceFiction": "Science-fiction", - "War": "Guerre", - "Kids": "Jeunesse", - "Thriller": "Thriller", - "Western": "Western", - "Politics": "Politique", - "Soap": "Soap", - "Talk": "Talkshow", - "Animation": "Animation", - "News": "Nouveautés", - "Reality": "Télé-réalité" + "action": "Action", + "adventure": "Aventure", + "comedy": "Comédie", + "documentary": "Documentaire", + "drama": "Drame", + "family": "Famille", + "fantasy": "Fantastique", + "history": "Histoire", + "crime": "Scène de crime", + "horror": "Horreur", + "music": "Musique", + "mystery": "Mystère", + "romance": "Romance", + "science-fiction": "Science-fiction", + "war": "Guerre", + "kids": "Jeunesse", + "thriller": "Thriller", + "western": "Western", + "politics": "Politique", + "soap": "Soap", + "talk": "Talkshow", + "animation": "Animation", + "reality": "Télé-réalité" } } diff --git a/front/public/translations/gl.json b/front/public/translations/gl.json index e1a4c287..f81c060e 100644 --- a/front/public/translations/gl.json +++ b/front/public/translations/gl.json @@ -16,7 +16,7 @@ "show": { "play": "Reproducir", "trailer": "Reproducir tráiler", - "studio": "Estudio", + "studios": "Estudio", "genre": "Xéneros", "genre-none": "Sen xéneros", "staff": "Persoal", @@ -70,30 +70,29 @@ "switchToList": "" }, "genres": { - "Action": "", - "Adventure": "", - "Animation": "", - "Comedy": "", - "Crime": "", - "Documentary": "", - "Drama": "", - "Family": "", - "Fantasy": "", - "History": "", - "Horror": "", - "Music": "", - "Mystery": "", - "Romance": "", - "ScienceFiction": "", - "Thriller": "", - "War": "", - "Western": "", - "Kids": "", - "News": "", - "Reality": "", - "Soap": "", - "Talk": "", - "Politics": "" + "action": "", + "adventure": "", + "animation": "", + "comedy": "", + "crime": "", + "documentary": "", + "drama": "", + "family": "", + "fantasy": "", + "history": "", + "horror": "", + "music": "", + "mystery": "", + "romance": "", + "science-fiction": "", + "thriller": "", + "war": "", + "western": "", + "kids": "", + "reality": "", + "soap": "", + "talk": "", + "politics": "" }, "misc": { "settings": "", diff --git a/front/public/translations/is.json b/front/public/translations/is.json index 256973be..46d0ffff 100644 --- a/front/public/translations/is.json +++ b/front/public/translations/is.json @@ -16,7 +16,7 @@ "show": { "play": "", "trailer": "", - "studio": "", + "studios": "", "genre": "", "genre-none": "", "staff": "", @@ -70,30 +70,29 @@ "switchToList": "" }, "genres": { - "Action": "", - "Adventure": "", - "Animation": "", - "Comedy": "", - "Crime": "", - "Documentary": "", - "Drama": "", - "Family": "", - "Fantasy": "", - "History": "", - "Horror": "", - "Music": "", - "Mystery": "", - "Romance": "", - "ScienceFiction": "", - "Thriller": "", - "War": "", - "Western": "", - "Kids": "", - "News": "", - "Reality": "", - "Soap": "", - "Talk": "", - "Politics": "" + "action": "", + "adventure": "", + "animation": "", + "comedy": "", + "crime": "", + "documentary": "", + "drama": "", + "family": "", + "fantasy": "", + "history": "", + "horror": "", + "music": "", + "mystery": "", + "romance": "", + "science-fiction": "", + "thriller": "", + "war": "", + "western": "", + "kids": "", + "reality": "", + "soap": "", + "talk": "", + "politics": "" }, "misc": { "settings": "", diff --git a/front/public/translations/it.json b/front/public/translations/it.json index fc013eeb..842575c2 100644 --- a/front/public/translations/it.json +++ b/front/public/translations/it.json @@ -16,7 +16,7 @@ "show": { "play": "Riproduci", "trailer": "Riproduci Trailer", - "studio": "Studio", + "studios": "Studio", "genre": "Generi", "genre-none": "Nessun genere", "staff": "Staff", @@ -252,29 +252,28 @@ } }, "genres": { - "Mystery": "Mistero", - "Kids": "Bambini", - "Western": "Western", - "History": "Storico", - "Romance": "Romantico", - "ScienceFiction": "Fantascienza", - "Thriller": "Thriller", - "War": "Guerra", - "Animation": "Animazione", - "Action": "Azione", - "Adventure": "Avventura", - "Comedy": "Commedia", - "Crime": "Criminale", - "Documentary": "Documentario", - "Drama": "Drammatico", - "Family": "Per famiglie", - "Horror": "Orrore", - "Music": "Musica", - "News": "Notizie", - "Reality": "Reality", - "Soap": "Telenovela", - "Talk": "Talk Show", - "Politics": "Politica", - "Fantasy": "Fantasia" + "mystery": "Mistero", + "kids": "Bambini", + "western": "Western", + "history": "Storico", + "romance": "Romantico", + "science-fiction": "Fantascienza", + "thriller": "Thriller", + "war": "Guerra", + "animation": "Animazione", + "action": "Azione", + "adventure": "Avventura", + "comedy": "Commedia", + "crime": "Criminale", + "documentary": "Documentario", + "drama": "Drammatico", + "family": "Per famiglie", + "horror": "Orrore", + "music": "Musica", + "reality": "Reality", + "soap": "Telenovela", + "talk": "Talk Show", + "politics": "Politica", + "fantasy": "Fantasia" } } diff --git a/front/public/translations/ko.json b/front/public/translations/ko.json index 77fb6310..77a9f2be 100644 --- a/front/public/translations/ko.json +++ b/front/public/translations/ko.json @@ -16,7 +16,7 @@ "show": { "play": "재생", "trailer": "예고편 재생", - "studio": "스튜디오", + "studios": "스튜디오", "genre": "장르", "genre-none": "장르 없음", "staff": "스태프", @@ -70,30 +70,29 @@ "switchToList": "리스트 뷰로 전환하기" }, "genres": { - "Action": "액션", - "Adventure": "모험", - "Animation": "애니메이션", - "Comedy": "코미디", - "Crime": "범죄", - "Documentary": "다큐멘터리", - "Drama": "드라마", - "Family": "가족", - "Fantasy": "판타지", - "History": "역사", - "Horror": "호러", - "Music": "음악", - "Mystery": "미스터리", - "Romance": "로맨스", - "ScienceFiction": "SF", - "Thriller": "스릴러", - "War": "전쟁", - "Western": "서부", - "Kids": "키즈", - "News": "뉴스", - "Reality": "리얼리티", - "Soap": "신파", - "Talk": "토크", - "Politics": "정치" + "action": "액션", + "adventure": "모험", + "animation": "애니메이션", + "comedy": "코미디", + "crime": "범죄", + "documentary": "다큐멘터리", + "drama": "드라마", + "family": "가족", + "fantasy": "판타지", + "history": "역사", + "horror": "호러", + "music": "음악", + "mystery": "미스터리", + "romance": "로맨스", + "science-fiction": "SF", + "thriller": "스릴러", + "war": "전쟁", + "western": "서부", + "kids": "키즈", + "reality": "리얼리티", + "soap": "신파", + "talk": "토크", + "politics": "정치" }, "misc": { "settings": "설정", diff --git a/front/public/translations/ml.json b/front/public/translations/ml.json index 16f4aeee..63e4dc3d 100644 --- a/front/public/translations/ml.json +++ b/front/public/translations/ml.json @@ -16,7 +16,7 @@ "show": { "play": "", "trailer": "", - "studio": "", + "studios": "", "genre": "", "genre-none": "", "staff": "", @@ -70,30 +70,29 @@ "switchToList": "" }, "genres": { - "Action": "", - "Adventure": "", - "Animation": "", - "Comedy": "", - "Crime": "", - "Documentary": "", - "Drama": "", - "Family": "", - "Fantasy": "", - "History": "", - "Horror": "", - "Music": "", - "Mystery": "", - "Romance": "", - "ScienceFiction": "", - "Thriller": "", - "War": "", - "Western": "", - "Kids": "", - "News": "", - "Reality": "", - "Soap": "", - "Talk": "", - "Politics": "" + "action": "", + "adventure": "", + "animation": "", + "comedy": "", + "crime": "", + "documentary": "", + "drama": "", + "family": "", + "fantasy": "", + "history": "", + "horror": "", + "music": "", + "mystery": "", + "romance": "", + "science-fiction": "", + "thriller": "", + "war": "", + "western": "", + "kids": "", + "reality": "", + "soap": "", + "talk": "", + "politics": "" }, "misc": { "settings": "", diff --git a/front/public/translations/nl.json b/front/public/translations/nl.json index 55a3830e..a481f1a3 100644 --- a/front/public/translations/nl.json +++ b/front/public/translations/nl.json @@ -16,7 +16,7 @@ "show": { "play": "Speel af", "trailer": "Speel trailer af", - "studio": "Studio", + "studios": "Studio", "genre": "Genres", "genre-none": "Geen genres", "staff": "Personeel", @@ -70,30 +70,29 @@ "switchToList": "Wissel naar lijstweergave" }, "genres": { - "Action": "Actie", - "Adventure": "Aventuur", - "Animation": "Animatie", - "Comedy": "Komedie", - "Crime": "Crime", - "Documentary": "Documantaire", - "Drama": "Drama", - "Family": "Famillie", - "Fantasy": "Fantasie", - "History": "Geschiedenis", - "Horror": "Horror", - "Music": "Muziek", - "Mystery": "Mysterie", - "Romance": "Romantiek", - "ScienceFiction": "Science Fiction", - "Thriller": "", - "War": "Oorlog", - "Western": "Western", - "Kids": "Kinderen", - "News": "Nieuws", - "Reality": "", - "Soap": "Soap", - "Talk": "Talk", - "Politics": "Politiek" + "action": "Actie", + "adventure": "Aventuur", + "animation": "Animatie", + "comedy": "Komedie", + "crime": "Crime", + "documentary": "Documantaire", + "drama": "Drama", + "family": "Famillie", + "fantasy": "Fantasie", + "history": "Geschiedenis", + "horror": "Horror", + "music": "Muziek", + "mystery": "Mysterie", + "romance": "Romantiek", + "science-fiction": "Science Fiction", + "thriller": "", + "war": "Oorlog", + "western": "Western", + "kids": "Kinderen", + "reality": "", + "soap": "Soap", + "talk": "Talk", + "politics": "Politiek" }, "misc": { "settings": "Instellingen", diff --git a/front/public/translations/pl.json b/front/public/translations/pl.json index 7cab4c1e..952e4b03 100644 --- a/front/public/translations/pl.json +++ b/front/public/translations/pl.json @@ -27,7 +27,7 @@ "null": "Oznacz jako nieobejrzane" }, "season": "Sezon {{number}}", - "studio": "Studio", + "studios": "Studio", "genre": "Gatunek", "staff": "Obsada", "noOverview": "Brak dostępnego podsumowania", @@ -210,30 +210,29 @@ "empty": "Nie znaleziono wyniku. Spróbuj użyć innego zapytania." }, "genres": { - "Action": "Akcja", - "Adventure": "Przygodowy", - "Animation": "Animacja", - "Comedy": "Komedia", - "Crime": "Kryminał", - "Documentary": "Dokument", - "Drama": "Dramat", - "Family": "Rodzinny", - "Fantasy": "Fantastyka", - "History": "Historyczny", - "Horror": "Horror", - "Music": "Muzyczny", - "Mystery": "Tajemnica", - "Romance": "Romans", - "ScienceFiction": "Sci-Fi", - "Thriller": "Dreszczowiec", - "War": "Wojenny", - "Western": "Dziki Zachód", - "Kids": "Dziecięcy", - "News": "Nowy", - "Reality": "Realny", - "Soap": "Opera mydlana", - "Talk": "Dyskusja", - "Politics": "Polityczny" + "action": "Akcja", + "adventure": "Przygodowy", + "animation": "Animacja", + "comedy": "Komedia", + "crime": "Kryminał", + "documentary": "Dokument", + "drama": "Dramat", + "family": "Rodzinny", + "fantasy": "Fantastyka", + "history": "Historyczny", + "horror": "Horror", + "music": "Muzyczny", + "mystery": "Tajemnica", + "romance": "Romans", + "science-fiction": "Sci-Fi", + "thriller": "Dreszczowiec", + "war": "Wojenny", + "western": "Dziki Zachód", + "kids": "Dziecięcy", + "reality": "Realny", + "soap": "Opera mydlana", + "talk": "Dyskusja", + "politics": "Polityczny" }, "navbar": { "home": "Ekran Główny", diff --git a/front/public/translations/pt-BR.json b/front/public/translations/pt-BR.json index 415dbfb9..a682c94b 100644 --- a/front/public/translations/pt-BR.json +++ b/front/public/translations/pt-BR.json @@ -16,7 +16,7 @@ "show": { "play": "Reproduzir", "trailer": "Reproduzir Trailer", - "studio": "Estúdio", + "studios": "Estúdio", "genre": "Gêneros", "genre-none": "Nenhum gênero", "staff": "Equipe", @@ -70,30 +70,29 @@ "switchToList": "Mudara para visualização de lista" }, "genres": { - "Action": "Ação", - "Adventure": "Aventura", - "Animation": "Animação", - "Comedy": "Comédia", - "Crime": "Crime", - "Documentary": "Documentário", - "Drama": "Drama", - "Family": "Família", - "Fantasy": "Fantasia", - "History": "História", - "Horror": "Terror", - "Music": "Música", - "Mystery": "Mistério", - "Romance": "Romance", - "ScienceFiction": "Ficção cientifica", - "Thriller": "Suspense", - "War": "Guerra", - "Western": "Faroeste", - "Kids": "Infantil", - "News": "Notícias", - "Reality": "Realidade", - "Soap": "Novela", - "Talk": "Entrevista", - "Politics": "Política" + "action": "Ação", + "adventure": "Aventura", + "animation": "Animação", + "comedy": "Comédia", + "crime": "Crime", + "documentary": "Documentário", + "drama": "Drama", + "family": "Família", + "fantasy": "Fantasia", + "history": "História", + "horror": "Terror", + "music": "Música", + "mystery": "Mistério", + "romance": "Romance", + "science-fiction": "Ficção cientifica", + "thriller": "Suspense", + "war": "Guerra", + "western": "Faroeste", + "kids": "Infantil", + "reality": "Realidade", + "soap": "Novela", + "talk": "Entrevista", + "politics": "Política" }, "misc": { "settings": "Configurações", diff --git a/front/public/translations/pt.json b/front/public/translations/pt.json index 4c6dacb8..1c59f1e2 100644 --- a/front/public/translations/pt.json +++ b/front/public/translations/pt.json @@ -16,7 +16,7 @@ "show": { "play": "Reproduzir", "trailer": "Reproduzir trailer", - "studio": "Estúdio", + "studios": "Estúdio", "genre": "Géneros", "genre-none": "Nenhum género", "staff": "Equipa", @@ -70,30 +70,29 @@ "switchToList": "Mudara para visualização de lista" }, "genres": { - "Action": "Ação", - "Adventure": "Aventura", - "Animation": "Animação", - "Comedy": "Comédia", - "Crime": "Crime", - "Documentary": "Documentário", - "Drama": "Drama", - "Family": "Família", - "Fantasy": "Fantasia", - "History": "História", - "Horror": "Terror", - "Music": "Música", - "Mystery": "Mistério", - "Romance": "Romance", - "ScienceFiction": "Ficção cientifica", - "Thriller": "Suspense", - "War": "Guerra", - "Western": "Faroeste", - "Kids": "Infantil", - "News": "Notícias", - "Reality": "Realidade", - "Soap": "Novela", - "Talk": "Entrevista", - "Politics": "Política" + "action": "Ação", + "adventure": "Aventura", + "animation": "Animação", + "comedy": "Comédia", + "crime": "Crime", + "documentary": "Documentário", + "drama": "Drama", + "family": "Família", + "fantasy": "Fantasia", + "history": "História", + "horror": "Terror", + "music": "Música", + "mystery": "Mistério", + "romance": "Romance", + "science-fiction": "Ficção cientifica", + "thriller": "Suspense", + "war": "Guerra", + "western": "Faroeste", + "kids": "Infantil", + "reality": "Realidade", + "soap": "Novela", + "talk": "Entrevista", + "politics": "Política" }, "misc": { "settings": "Configurações", diff --git a/front/public/translations/ro.json b/front/public/translations/ro.json index 6edf159c..85f42e99 100644 --- a/front/public/translations/ro.json +++ b/front/public/translations/ro.json @@ -16,7 +16,7 @@ "show": { "play": "Redați", "trailer": "Redați trailerul", - "studio": "Studio", + "studios": "Studio", "genre": "Genuri", "genre-none": "Fără genuri", "staff": "Personalul", @@ -70,30 +70,29 @@ "switchToList": "Comutați la vizualizarea listă" }, "genres": { - "Action": "Acţiune", - "Adventure": "Aventură", - "Animation": "Animaţie", - "Comedy": "Comedie", - "Crime": "Crima", - "Documentary": "Documentar", - "Drama": "Dramă", - "Family": "Familial", - "Fantasy": "Fantezie", - "History": "Istorie", - "Horror": "Groază", - "Music": "Muzică", - "Mystery": "Mister", - "Romance": "Romantism", - "ScienceFiction": "Operă științifico-fantastică", - "Thriller": "Thriller", - "War": "Război", - "Western": "de vest", - "Kids": "Copii", - "News": "Ştiri", - "Reality": "Realitate", - "Soap": "Novela", - "Talk": "Vorbi", - "Politics": "Politică" + "action": "Acţiune", + "adventure": "Aventură", + "animation": "Animaţie", + "comedy": "Comedie", + "crime": "Crima", + "documentary": "Documentar", + "drama": "Dramă", + "family": "Familial", + "fantasy": "Fantezie", + "history": "Istorie", + "horror": "Groază", + "music": "Muzică", + "mystery": "Mister", + "romance": "Romantism", + "science-fiction": "Operă științifico-fantastică", + "thriller": "Thriller", + "war": "Război", + "western": "de vest", + "kids": "Copii", + "reality": "Realitate", + "soap": "Novela", + "talk": "Vorbi", + "politics": "Politică" }, "misc": { "settings": "Setări", diff --git a/front/public/translations/ru.json b/front/public/translations/ru.json index 95c8b92c..a75cff6b 100644 --- a/front/public/translations/ru.json +++ b/front/public/translations/ru.json @@ -16,7 +16,7 @@ "show": { "play": "Начать просмотр", "trailer": "Просмотр трейлера", - "studio": "Студия", + "studios": "Студия", "genre": "Жанры", "genre-none": "Жанры отсутствуют", "staff": "Команда", @@ -70,30 +70,29 @@ "switchToList": "Перейти в режим списка" }, "genres": { - "Action": "Экшн", - "Adventure": "Приключение", - "Animation": "Мультфильм", - "Comedy": "Комедия", - "Crime": "Криминал", - "Documentary": "Документальный", - "Drama": "Драма", - "Family": "Семейный", - "Fantasy": "Фэнтези", - "History": "Исторический", - "Horror": "Ужасы", - "Music": "Музыкальный", - "Mystery": "Мистический", - "Romance": "Романтический", - "ScienceFiction": "Научная фантастика", - "Thriller": "Триллер", - "War": "Военный", - "Western": "Вестерн", - "Kids": "Детский", - "News": "Новости", - "Reality": "Реалити-шоу", - "Soap": "Мыльная опера", - "Talk": "Ток-шоу", - "Politics": "Политика" + "action": "Экшн", + "adventure": "Приключение", + "animation": "Мультфильм", + "comedy": "Комедия", + "crime": "Криминал", + "documentary": "Документальный", + "drama": "Драма", + "family": "Семейный", + "fantasy": "Фэнтези", + "history": "Исторический", + "horror": "Ужасы", + "music": "Музыкальный", + "mystery": "Мистический", + "romance": "Романтический", + "science-fiction": "Научная фантастика", + "thriller": "Триллер", + "war": "Военный", + "western": "Вестерн", + "kids": "Детский", + "reality": "Реалити-шоу", + "soap": "Мыльная опера", + "talk": "Ток-шоу", + "politics": "Политика" }, "misc": { "settings": "Настройки", diff --git a/front/public/translations/ta.json b/front/public/translations/ta.json index e62875c9..b214db91 100644 --- a/front/public/translations/ta.json +++ b/front/public/translations/ta.json @@ -16,7 +16,7 @@ "show": { "play": "விளையாடுங்கள்", "trailer": "டிரெய்லர் விளையாடுங்கள்", - "studio": "ச்டுடியோ", + "studios": "ச்டுடியோ", "genre": "வகைகள்", "genre-none": "வகைகள் இல்லை", "staff": "பணியாளர்", @@ -70,30 +70,29 @@ "switchToList": "பட்டியல் பார்வைக்கு மாறவும்" }, "genres": { - "Action": "செயல்", - "Adventure": "துணிவு", - "Animation": "அனிமேசன்", - "Comedy": "நகைச்சுவை", - "Crime": "குற்றம்", - "Documentary": "ஆவணப்படம்", - "Drama": "நாடகம்", - "Family": "குடும்பம்", - "Fantasy": "கற்பனை", - "History": "வரலாறு", - "Horror": "அதிர்ச்சி", - "Music": "இசை", - "Mystery": "மர்மம்", - "Romance": "காதல்", - "ScienceFiction": "அறிவியல் புனைகதை", - "Thriller": "த்ரில்லர்", - "War": "போர்", - "Western": "மேற்கு", - "Kids": "குழந்தைகள்", - "News": "செய்தி", - "Reality": "உண்மை", - "Soap": "சோப்பு", - "Talk": "பேச்சு", - "Politics": "அரசியல்" + "action": "செயல்", + "adventure": "துணிவு", + "animation": "அனிமேசன்", + "comedy": "நகைச்சுவை", + "crime": "குற்றம்", + "documentary": "ஆவணப்படம்", + "drama": "நாடகம்", + "family": "குடும்பம்", + "fantasy": "கற்பனை", + "history": "வரலாறு", + "horror": "அதிர்ச்சி", + "music": "இசை", + "mystery": "மர்மம்", + "romance": "காதல்", + "science-fiction": "அறிவியல் புனைகதை", + "thriller": "த்ரில்லர்", + "war": "போர்", + "western": "மேற்கு", + "kids": "குழந்தைகள்", + "reality": "உண்மை", + "soap": "சோப்பு", + "talk": "பேச்சு", + "politics": "அரசியல்" }, "misc": { "settings": "அமைப்புகள்", diff --git a/front/public/translations/tr.json b/front/public/translations/tr.json index 2f4022f2..171d5884 100644 --- a/front/public/translations/tr.json +++ b/front/public/translations/tr.json @@ -16,7 +16,7 @@ "show": { "play": "İzle", "trailer": "Fragmanı İzle", - "studio": "Stüdyo", + "studios": "Stüdyo", "genre": "Kategoriler", "genre-none": "Kategori bilgisi mevcut değil", "staff": "Kadro", @@ -259,29 +259,28 @@ } }, "genres": { - "Adventure": "Macera", - "Comedy": "Komedi", - "Crime": "Suç", - "Documentary": "Belgesel", - "Drama": "Dram", - "Family": "Aile", - "Fantasy": "Fantezi", - "Horror": "Korku", - "Music": "Müzikal", - "Mystery": "Gizem", - "Romance": "Romantik", - "ScienceFiction": "Bilim Kurgu", - "Thriller": "Gerilim", - "War": "Savaş", - "Western": "Kovboy", - "Kids": "Çocuk", - "News": "Haber", - "Reality": "Reality", - "Soap": "Melodrama", - "Talk": "Söyleşi", - "Action": "Aksiyon", - "Animation": "Animasyon", - "History": "Tarih", - "Politics": "Siyaset" + "adventure": "Macera", + "comedy": "Komedi", + "crime": "Suç", + "documentary": "Belgesel", + "drama": "Dram", + "family": "Aile", + "fantasy": "Fantezi", + "horror": "Korku", + "music": "Müzikal", + "mystery": "Gizem", + "romance": "Romantik", + "science-fiction": "Bilim Kurgu", + "thriller": "Gerilim", + "war": "Savaş", + "western": "Kovboy", + "kids": "Çocuk", + "reality": "Reality", + "soap": "Melodrama", + "talk": "Söyleşi", + "action": "Aksiyon", + "animation": "Animasyon", + "history": "Tarih", + "politics": "Siyaset" } } diff --git a/front/public/translations/uk.json b/front/public/translations/uk.json index 452d8561..5f893ccf 100644 --- a/front/public/translations/uk.json +++ b/front/public/translations/uk.json @@ -16,7 +16,7 @@ "show": { "play": "Відтворити", "trailer": "Відтворити трейлер", - "studio": "Студія", + "studios": "Студія", "genre": "Жанри", "genre-none": "Жанри відсутні", "staff": "Команда", @@ -261,29 +261,28 @@ } }, "genres": { - "Horror": "Жахи", - "War": "Війна", - "Comedy": "Комедія", - "Crime": "Кримінал", - "Documentary": "Документальний", - "Family": "Сімейний", - "Fantasy": "Фентезі", - "History": "Історія", - "Music": "Музика", - "Mystery": "Містика", - "Romance": "Романтика", - "ScienceFiction": "Наукова фантастика", - "Thriller": "Трилер", - "Western": "Вестерн", - "Kids": "Діти", - "News": "Новини", - "Reality": "Реаліті", - "Soap": "Мильна опера", - "Talk": "Ток-шоу", - "Politics": "Політика", - "Action": "Екшн", - "Adventure": "Пригода", - "Animation": "Мультфільм", - "Drama": "Драма" + "horror": "Жахи", + "war": "Війна", + "comedy": "Комедія", + "crime": "Кримінал", + "documentary": "Документальний", + "family": "Сімейний", + "fantasy": "Фентезі", + "history": "Історія", + "music": "Музика", + "mystery": "Містика", + "romance": "Романтика", + "science-fiction": "Наукова фантастика", + "thriller": "Трилер", + "western": "Вестерн", + "kids": "Діти", + "reality": "Реаліті", + "soap": "Мильна опера", + "talk": "Ток-шоу", + "politics": "Політика", + "action": "Екшн", + "adventure": "Пригода", + "animation": "Мультфільм", + "drama": "Драма" } } diff --git a/front/public/translations/zh.json b/front/public/translations/zh.json index 4350e171..b1e88278 100644 --- a/front/public/translations/zh.json +++ b/front/public/translations/zh.json @@ -16,7 +16,7 @@ "show": { "play": "播放", "trailer": "播放预告片", - "studio": "制作公司", + "studios": "制作公司", "genre": "类型", "genre-none": "无类型", "staff": "制作人员", @@ -257,29 +257,28 @@ } }, "genres": { - "Action": "动作片", - "Adventure": "冒险", - "Animation": "动漫", - "Comedy": "喜剧片", - "Crime": "犯罪片", - "Documentary": "纪录片", - "Drama": "戏剧", - "Family": "家庭", - "Fantasy": "奇幻", - "History": "历史", - "Horror": "恐怖", - "Music": "音乐", - "Mystery": "悬疑", - "Romance": "浪漫", - "ScienceFiction": "科幻", - "Thriller": "惊悚", - "War": "战争", - "Western": "西部", - "Kids": "儿童", - "News": "新闻", - "Reality": "现实", - "Soap": "肥皂剧", - "Talk": "访谈", - "Politics": "政治" + "action": "动作片", + "adventure": "冒险", + "animation": "动漫", + "comedy": "喜剧片", + "crime": "犯罪片", + "documentary": "纪录片", + "drama": "戏剧", + "family": "家庭", + "fantasy": "奇幻", + "history": "历史", + "horror": "恐怖", + "music": "音乐", + "mystery": "悬疑", + "romance": "浪漫", + "science-fiction": "科幻", + "thriller": "惊悚", + "war": "战争", + "western": "西部", + "kids": "儿童", + "reality": "现实", + "soap": "肥皂剧", + "talk": "访谈", + "politics": "政治" } } From 39cfd501ac64d7bff4c1671e800b9aca6a53b45e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 8 Nov 2025 20:33:18 +0100 Subject: [PATCH 4/9] Add automatic language detection and language setting --- front/app.config.ts | 7 ++ front/bun.lock | 6 + front/package.json | 2 + front/packages/ui/src/settings/general.tsx | 103 ------------------ front/public/translations/en.json | 3 +- front/src/providers/translations-detector.tsx | 3 + .../providers/translations-detector.web.tsx | 7 ++ front/src/providers/translations.native.tsx | 3 +- .../src/providers/translations.web.client.tsx | 31 +++--- front/src/ui/settings/general.tsx | 72 ++++++++++++ front/src/ui/settings/index.tsx | 6 +- 11 files changed, 120 insertions(+), 123 deletions(-) delete mode 100644 front/packages/ui/src/settings/general.tsx create mode 100644 front/src/providers/translations-detector.tsx create mode 100644 front/src/providers/translations-detector.web.tsx create mode 100644 front/src/ui/settings/general.tsx 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 ( - - - changeLanguage(value)} - values={["system", ...Object.keys(i18n.options.resources!)]} - getLabel={(key) => - key === "system" ? t("settings.general.language.system") : (getLanguageName(key) ?? key) - } - /> - - - ); -}; - -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 ( + + {/* */} + {/* i18n.changeLanguage(value)} + values={supportedLanguages} + getLabel={(key) => getLanguageName(key) ?? key} + /> + + + ); +}; + +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 && } */} - {/* */} + ); }; From 6bb905b3880b3c1be6ed907645fdffe70103394b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 9 Nov 2025 16:34:26 +0100 Subject: [PATCH 5/9] Rework settings page --- front/packages/ui/src/settings/oidc.tsx | 128 ---------- front/src/app/(app)/settings.tsx | 3 + front/src/app/(public)/_layout.tsx | 3 +- front/src/models/user.ts | 72 +++--- front/src/primitives/alert.web.tsx | 20 -- front/src/primitives/index.ts | 6 +- front/src/primitives/popup.tsx | 25 +- front/src/providers/native-providers.web.tsx | 3 +- front/src/query/query.tsx | 2 +- .../ui/src => src/ui}/settings/account.tsx | 227 +++++++++--------- front/src/ui/settings/base.tsx | 27 ++- front/src/ui/settings/general.tsx | 2 +- front/src/ui/settings/index.tsx | 8 +- front/src/ui/settings/oidc.tsx | 108 +++++++++ .../ui/src => src/ui}/settings/playback.tsx | 52 ++-- 15 files changed, 328 insertions(+), 358 deletions(-) delete mode 100644 front/packages/ui/src/settings/oidc.tsx create mode 100644 front/src/app/(app)/settings.tsx rename front/{packages/ui/src => src/ui}/settings/account.tsx (59%) create mode 100644 front/src/ui/settings/oidc.tsx rename front/{packages/ui/src => src/ui}/settings/playback.tsx (66%) diff --git a/front/packages/ui/src/settings/oidc.tsx b/front/packages/ui/src/settings/oidc.tsx deleted file mode 100644 index e4c383e5..00000000 --- a/front/packages/ui/src/settings/oidc.tsx +++ /dev/null @@ -1,128 +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 { - type QueryIdentifier, - type ServerInfo, - ServerInfoP, - queryFn, - useAccount, - useFetch, -} from "@kyoo/models"; -import { Button, IconButton, Link, Skeleton, tooltip, ts } from "@kyoo/primitives"; -import { useTranslation } from "react-i18next"; -import { ImageBackground } from "react-native"; -import { rem, useYoshiki } from "yoshiki/native"; -import { ErrorView } from "../../../../src/ui/errors"; -import { Preference, SettingsContainer } from "./base"; - -import Badge from "@material-symbols/svg-400/outlined/badge.svg"; -import Remove from "@material-symbols/svg-400/outlined/close.svg"; -import OpenProfile from "@material-symbols/svg-400/outlined/open_in_new.svg"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export const OidcSettings = () => { - const account = useAccount()!; - const { css } = useYoshiki(); - const { t } = useTranslation(); - const { data, error } = useFetch(OidcSettings.query()); - const queryClient = useQueryClient(); - const { mutateAsync: unlinkAccount } = useMutation({ - mutationFn: async (provider: string) => - await queryFn({ - path: ["auth", "login", provider], - method: "DELETE", - }), - onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["auth", "me"] }), - }); - - return ( - - {error ? ( - - ) : data ? ( - Object.entries(data.oidc).map(([id, x]) => { - const acc = account.externalId[id]; - return ( - - ) - } - > - {acc ? ( - <> - {acc.profileUrl && ( - - )} - unlinkAccount(id)} - {...tooltip(t("settings.oidc.delete", { provider: x.displayName }))} - /> - - ) : ( -