From adbf11969e439d5a3120a57b632303d4da760e28 Mon Sep 17 00:00:00 2001 From: Jip Fr Date: Wed, 3 Jan 2024 00:55:42 +0100 Subject: [PATCH 01/35] Fix player UI not always vanishing (or so we hope :D) --- src/components/player/atoms/ProgressBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/player/atoms/ProgressBar.tsx b/src/components/player/atoms/ProgressBar.tsx index ce3ecebe..dfba7d08 100644 --- a/src/components/player/atoms/ProgressBar.tsx +++ b/src/components/player/atoms/ProgressBar.tsx @@ -39,7 +39,7 @@ function ThumbnailDisplay(props: { at: number; show: boolean }) { if (!props.show || !currentThumbnail) return null; return ( -
+
Date: Wed, 3 Jan 2024 20:06:08 +0100 Subject: [PATCH 02/35] Implement new country code system and new language code system --- package.json | 2 +- pnpm-lock.yaml | 11 +- src/assets/languages.ts | 2 - src/components/FlagIcon.tsx | 41 +--- src/components/form/Dropdown.tsx | 2 +- .../player/atoms/settings/CaptionsView.tsx | 11 +- .../player/atoms/settings/SettingsMenu.tsx | 4 +- src/components/player/utils/language.ts | 14 -- src/index.tsx | 5 +- src/pages/parts/settings/LocalePart.tsx | 10 +- src/setup/i18n.ts | 34 +--- src/stores/language/index.tsx | 17 +- src/utils/language.ts | 188 ++++++++++++++++++ src/utils/sortLangCodes.ts | 12 -- vite.config.mts | 2 +- 15 files changed, 243 insertions(+), 112 deletions(-) delete mode 100644 src/components/player/utils/language.ts create mode 100644 src/utils/language.ts delete mode 100644 src/utils/sortLangCodes.ts diff --git a/package.json b/package.json index 544f7500..c3e1b2d8 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@formkit/auto-animate": "^0.8.1", "@headlessui/react": "^1.7.17", + "@ladjs/country-language": "^1.0.3", "@movie-web/providers": "^2.0.2", "@noble/hashes": "^1.3.3", "@react-spring/web": "^9.7.3", @@ -44,7 +45,6 @@ "hls.js": "^1.4.14", "i18next": "^23.7.11", "immer": "^10.0.3", - "iso-639-1": "^3.1.0", "jwt-decode": "^4.0.0", "lodash.isequal": "^4.5.0", "million": "^2.6.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ef1bada..6022bda6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: '@headlessui/react': specifier: ^1.7.17 version: 1.7.17(react-dom@18.2.0)(react@18.2.0) + '@ladjs/country-language': + specifier: ^1.0.3 + version: 1.0.3 '@movie-web/providers': specifier: ^2.0.2 version: 2.0.3 @@ -65,9 +68,6 @@ dependencies: immer: specifier: ^10.0.3 version: 10.0.3 - iso-639-1: - specifier: ^3.1.0 - version: 3.1.0 jwt-decode: specifier: ^4.0.0 version: 4.0.0 @@ -1912,6 +1912,11 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@ladjs/country-language@1.0.3: + resolution: {integrity: sha512-FJROu9/hh4eqVAGDyfL8vpv6Vb0qKHX1ozYLRZ+beUzD5xFf+3r0J+SVIWKviEa7W524Qvqou+ta1WrsRgzxGw==} + engines: {node: '>= 14'} + dev: false + /@movie-web/providers@2.0.3: resolution: {integrity: sha512-6UNk5EebiNjGoFTuyHuu0eZZTreRYv0cdsn52CVYjm6CXG63w4dMbx8ybxcvMUrDF3o8bWlqnlovG142sdOmNw==} dependencies: diff --git a/src/assets/languages.ts b/src/assets/languages.ts index 6af96bf7..baa36531 100644 --- a/src/assets/languages.ts +++ b/src/assets/languages.ts @@ -50,5 +50,3 @@ export const locales = { uk, }; export type Locales = keyof typeof locales; - -export const rtlLocales: Locales[] = ["he", "ar"]; diff --git a/src/components/FlagIcon.tsx b/src/components/FlagIcon.tsx index 58466819..96f9431e 100644 --- a/src/components/FlagIcon.tsx +++ b/src/components/FlagIcon.tsx @@ -1,53 +1,32 @@ import classNames from "classnames"; + +import { getCountryCodeForLocale } from "@/utils/language"; import "flag-icons/css/flag-icons.min.css"; export interface FlagIconProps { - countryCode?: string; + country?: string; + langCode?: string; } -// Country code overrides -const countryOverrides: Record = { - en: "gb", - cs: "cz", - el: "gr", - fa: "ir", - ko: "kr", - he: "il", - ze: "cn", - ar: "sa", - ja: "jp", - bs: "ba", - vi: "vn", - zh: "cn", - sl: "si", - sv: "se", - et: "ee", - ne: "np", - uk: "ua", - hi: "in", -}; - export function FlagIcon(props: FlagIconProps) { - let countryCode = - (props.countryCode || "")?.split("-").pop()?.toLowerCase() || ""; - if (countryOverrides[countryCode]) - countryCode = countryOverrides[countryCode]; + let countryCode: string | null = props.country ?? null; + if (props.langCode) countryCode = getCountryCodeForLocale(props.langCode); - if (countryCode === "tok") + if (props.langCode === "tok") return (
); - if (countryCode === "pirate") + if (props.langCode === "pirate") return (
); - if (countryCode === "minion") + if (props.langCode === "minion") return (
@@ -66,7 +45,7 @@ export function FlagIcon(props: FlagIconProps) { className={classNames( "!w-8 h-6 rounded overflow-hidden bg-cover bg-center block fi", backgroundClass, - props.countryCode ? `fi-${countryCode}` : undefined, + countryCode ? `fi-${countryCode}` : undefined, )} /> ); diff --git a/src/components/form/Dropdown.tsx b/src/components/form/Dropdown.tsx index febe923f..df647b5f 100644 --- a/src/components/form/Dropdown.tsx +++ b/src/components/form/Dropdown.tsx @@ -17,7 +17,7 @@ interface DropdownProps { export function Dropdown(props: DropdownProps) { return ( -
+
{() => ( <> diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index b640f9cd..dc9a112f 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -10,12 +10,14 @@ import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; import { Input } from "@/components/player/internals/ContextMenu/Input"; import { SelectableLink } from "@/components/player/internals/ContextMenu/Links"; -import { getLanguageFromIETF } from "@/components/player/utils/language"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { CaptionListItem } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; import { useSubtitleStore } from "@/stores/subtitles"; -import { sortLangCodes } from "@/utils/sortLangCodes"; +import { + getPrettyLanguageNameFromLocale, + sortLangCodes, +} from "@/utils/language"; export function CaptionOption(props: { countryCode?: string; @@ -37,7 +39,7 @@ export function CaptionOption(props: { className="flex items-center" > - + {props.children} @@ -89,7 +91,8 @@ function useSubtitleList(subs: CaptionListItem[], searchQuery: string) { return useMemo(() => { const input = subs.map((t) => ({ ...t, - languageName: getLanguageFromIETF(t.language) ?? unknownChoice, + languageName: + getPrettyLanguageNameFromLocale(t.language) ?? unknownChoice, })); const sorted = sortLangCodes(input.map((t) => t.language)); let results = input.sort((a, b) => { diff --git a/src/components/player/atoms/settings/SettingsMenu.tsx b/src/components/player/atoms/settings/SettingsMenu.tsx index c9ecdf52..8321c562 100644 --- a/src/components/player/atoms/settings/SettingsMenu.tsx +++ b/src/components/player/atoms/settings/SettingsMenu.tsx @@ -6,11 +6,11 @@ import { Toggle } from "@/components/buttons/Toggle"; import { Icon, Icons } from "@/components/Icon"; import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; -import { getLanguageFromIETF } from "@/components/player/utils/language"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; import { qualityToString } from "@/stores/player/utils/qualities"; import { useSubtitleStore } from "@/stores/subtitles"; +import { getPrettyLanguageNameFromLocale } from "@/utils/language"; export function SettingsMenu({ id }: { id: string }) { const { t } = useTranslation(); @@ -31,7 +31,7 @@ export function SettingsMenu({ id }: { id: string }) { const { toggleLastUsed } = useCaptions(); const selectedLanguagePretty = selectedCaptionLanguage - ? getLanguageFromIETF(selectedCaptionLanguage) ?? + ? getPrettyLanguageNameFromLocale(selectedCaptionLanguage) ?? t("player.menus.subtitles.unknownLanguage") : undefined; diff --git a/src/components/player/utils/language.ts b/src/components/player/utils/language.ts deleted file mode 100644 index bf31e786..00000000 --- a/src/components/player/utils/language.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getTag } from "@sozialhelden/ietf-language-tags"; - -export function getLanguageFromIETF(ietf: string): string | null { - const tag = getTag(ietf, true); - - const lang = tag?.language?.Description?.[0] ?? null; - if (!lang) return null; - - const region = tag?.region?.Description?.[0] ?? null; - let regionText = ""; - if (region) regionText = ` (${region})`; - - return `${lang}${regionText}`; -} diff --git a/src/index.tsx b/src/index.tsx index a0376611..d5fffaae 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,10 +23,9 @@ import { MigrationPart } from "@/pages/parts/migrations/MigrationPart"; import { LargeTextPart } from "@/pages/parts/util/LargeTextPart"; import App from "@/setup/App"; import { conf } from "@/setup/config"; -import i18n from "@/setup/i18n"; import { useAuthStore } from "@/stores/auth"; import { BookmarkSyncer } from "@/stores/bookmarks/BookmarkSyncer"; -import { useLanguageStore } from "@/stores/language"; +import { changeAppLanguage, useLanguageStore } from "@/stores/language"; import { ProgressSyncer } from "@/stores/progress/ProgressSyncer"; import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer"; import { ThemeProvider } from "@/stores/theme"; @@ -123,7 +122,7 @@ function AuthWrapper() { function MigrationRunner() { const status = useAsync(async () => { - i18n.changeLanguage(useLanguageStore.getState().language); + changeAppLanguage(useLanguageStore.getState().language); await initializeOldStores(); }, []); const { t } = useTranslation(); diff --git a/src/pages/parts/settings/LocalePart.tsx b/src/pages/parts/settings/LocalePart.tsx index ef9801b9..87819451 100644 --- a/src/pages/parts/settings/LocalePart.tsx +++ b/src/pages/parts/settings/LocalePart.tsx @@ -4,7 +4,7 @@ import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; import { Heading1 } from "@/components/utils/Text"; import { appLanguageOptions } from "@/setup/i18n"; -import { sortLangCodes } from "@/utils/sortLangCodes"; +import { getLocaleInfo, sortLangCodes } from "@/utils/language"; export function LocalePart(props: { language: string; @@ -17,11 +17,13 @@ export function LocalePart(props: { .sort((a, b) => sorted.indexOf(a.code) - sorted.indexOf(b.code)) .map((opt) => ({ id: opt.code, - name: `${opt.name} — ${opt.nativeName}`, - leftIcon: , + name: `${opt.name}${opt.nativeName ? ` — ${opt.nativeName}` : ""}`, + leftIcon: , })); - const selected = options.find((item) => item.id === props.language); + const selected = options.find( + (item) => item.id === getLocaleInfo(props.language)?.code, + ); return (
diff --git a/src/setup/i18n.ts b/src/setup/i18n.ts index 0110363a..ca263975 100644 --- a/src/setup/i18n.ts +++ b/src/setup/i18n.ts @@ -1,8 +1,8 @@ import i18n from "i18next"; -import ISO6391 from "iso-639-1"; import { initReactI18next } from "react-i18next"; import { locales } from "@/assets/languages"; +import { getLocaleInfo } from "@/utils/language"; // Languages const langCodes = Object.keys(locales); @@ -10,43 +10,15 @@ const resources = Object.fromEntries( Object.entries(locales).map((entry) => [entry[0], { translation: entry[1] }]), ); i18n.use(initReactI18next).init({ - fallbackLng: "en", + fallbackLng: "en-US", resources, interpolation: { escapeValue: false, // not needed for react as it escapes by default }, }); -const extraLanguages: Record< - string, - { - code: string; - name: string; - nativeName: string; - } -> = { - pirate: { - code: "pirate", - name: "Pirate", - nativeName: "Pirate Tongue", - }, - minion: { - code: "minion", - name: "Minion", - nativeName: "Minionese", - }, - tok: { - code: "tok", - name: "Toki pona", - nativeName: "Toki pona", - }, -}; - export const appLanguageOptions = langCodes.map((lang) => { - const extraLang = extraLanguages[lang]; - if (extraLang) return extraLang; - - const [langObj] = ISO6391.getLanguages([lang]); + const langObj = getLocaleInfo(lang); if (!langObj) throw new Error(`Language with code ${lang} cannot be found in database`); return langObj; diff --git a/src/stores/language/index.tsx b/src/stores/language/index.tsx index 9cee9d19..c901fddf 100644 --- a/src/stores/language/index.tsx +++ b/src/stores/language/index.tsx @@ -4,8 +4,8 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; -import { rtlLocales } from "@/assets/languages"; import i18n from "@/setup/i18n"; +import { getLocaleInfo } from "@/utils/language"; export interface LanguageStore { language: string; @@ -26,14 +26,25 @@ export const useLanguageStore = create( ), ); +export function changeAppLanguage(language: string) { + const lang = getLocaleInfo(language); + if (lang) i18n.changeLanguage(lang.code); +} + +export function isRightToLeft(language: string) { + const lang = getLocaleInfo(language); + if (!lang) return false; + return lang.isRtl; +} + export function LanguageProvider() { const language = useLanguageStore((s) => s.language); useEffect(() => { - i18n.changeLanguage(language); + changeAppLanguage(language); }, [language]); - const isRtl = rtlLocales.includes(language as any); + const isRtl = isRightToLeft(language); return ( diff --git a/src/utils/language.ts b/src/utils/language.ts new file mode 100644 index 00000000..420c8d93 --- /dev/null +++ b/src/utils/language.ts @@ -0,0 +1,188 @@ +import countryLanguages from "@ladjs/country-language"; +import { getTag } from "@sozialhelden/ietf-language-tags"; + +const languageOrder = ["en", "hi", "fr", "de", "nl", "pt"]; + +// mapping of language code to country code. +// multiple mappings can exist, since languages are spoken in multiple countries. +// This mapping purely exists to prioritize a country over another in languages. +// iso639_1 -> iso3166 Alpha-2 +const countryPriority: Record = { + en: "gb", + nl: "nl", + fr: "fr", + de: "de", + pt: "pt", + ar: "sa", + es: "es", + zh: "cn", +}; + +// list of iso639_1 Alpha-2 codes used as default languages +const defaultLanguageCodes: string[] = [ + "en-US", + "cs-CZ", + "de-DE", + "fr-FR", + "pt-BR", + "it-IT", + "nl-NL", + "pl-PL", + "tr-TR", + "vi-VN", + "zh-CN", + "he-IL", + "sv-SE", + "lv-LV", + "th-TH", + "ne-NP", + "ar-SA", + "es-ES", + "et-EE", +]; + +export interface LocaleInfo { + name: string; + nativeName?: string; + code: string; + isRtl?: boolean; +} + +interface LanguageObj { + countries: Array<{ + code_2: string; + code_3: string; + numCode: string; + }>; + direction: "RTL" | "LTR"; + name: string[]; + nativeName: string[]; + iso639_1: string; +} + +const extraLanguages: Record = { + pirate: { + code: "pirate", + name: "Pirate", + nativeName: "Pirate Tongue", + }, + minion: { + code: "minion", + name: "Minion", + nativeName: "Minionese", + }, + tok: { + code: "tok", + name: "Toki pona", + nativeName: "Toki pona", + }, +}; + +function populateLanguageCode(language: string): string { + if (language.includes("-")) return language; + if (language.length !== 2) return language; + return ( + defaultLanguageCodes.find((v) => v.startsWith(`${language}-`)) ?? language + ); +} + +/** + * @param locale idk what kinda code this takes, anytihhng in ietf format I guess + * @returns pretty format for language, null if it no info can be found for language + */ +export function getPrettyLanguageNameFromLocale(locale: string): string | null { + const tag = getTag(populateLanguageCode(locale), true); + + const lang = tag?.language?.Description?.[0] ?? null; + if (!lang) return null; + + const region = tag?.region?.Description?.[0] ?? null; + let regionText = ""; + if (region) regionText = ` (${region})`; + + return `${lang}${regionText}`; +} + +/** + * Sort locale codes by occurance, rest on alphabetical order + * @param langCodes list language codes to sort + * @returns sorted version of inputted list + */ +export function sortLangCodes(langCodes: string[]) { + const languagesOrder = [...languageOrder].reverse(); // Reverse is neccesary, not sure why + + const results = langCodes.sort((a, b) => { + const langOrderA = languagesOrder.findIndex( + (v) => a.startsWith(`${v}-`) || a === v, + ); + const langOrderB = languagesOrder.findIndex( + (v) => b.startsWith(`${v}-`) || b === v, + ); + if (langOrderA !== -1 || langOrderB !== -1) return langOrderB - langOrderA; + + return a.localeCompare(b); + }); + + return results; +} + +/** + * Get country code for locale + * @param locale input locale + * @returns country code or null + */ +export function getCountryCodeForLocale(locale: string): string | null { + let output: LanguageObj | null = null as any as LanguageObj; + const tag = getTag(locale, true); + if (!tag?.language?.Subtag) return null; + // this function isnt async, so its garuanteed to work like this + countryLanguages.getLanguage( + tag.language.Subtag, + (_err: string, lang: LanguageObj) => { + if (lang) output = lang; + }, + ); + if (!output) return null; + if (output.countries.length === 0) return null; + const priority = countryPriority[output.iso639_1.toLowerCase()]; + if (priority) { + const priotizedCountry = output.countries.find( + (v) => v.code_2.toLowerCase() === priority, + ); + if (priotizedCountry) return priotizedCountry.code_2.toLowerCase(); + } + return output.countries[0].code_2.toLowerCase(); +} + +/** + * Get information for a specific local + * @param locale local code + * @returns locale object + */ +export function getLocaleInfo(locale: string): LocaleInfo | null { + const realLocale = populateLanguageCode(locale); + const extraLang = extraLanguages[realLocale]; + if (extraLang) return extraLang; + + const tag = getTag(realLocale, true); + if (!tag?.language?.Subtag) return null; + + let output: LanguageObj | null = null as any as LanguageObj; + // this function isnt async, so its garuanteed to work like this + countryLanguages.getLanguage( + tag.language.Subtag, + (_err: string, lang: LanguageObj) => { + if (lang) output = lang; + }, + ); + if (!output) return null; + + return { + code: tag.parts.langtag ?? realLocale, + isRtl: output.direction === "RTL", + name: + output.name[0] + + (tag.region?.Description ? ` (${tag.region.Description[0]})` : ""), + nativeName: output.nativeName[0] ?? undefined, + }; +} diff --git a/src/utils/sortLangCodes.ts b/src/utils/sortLangCodes.ts deleted file mode 100644 index 57999e92..00000000 --- a/src/utils/sortLangCodes.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function sortLangCodes(langCodes: string[]) { - const languagesOrder = ["en", "hi", "fr", "de", "nl", "pt"].reverse(); // Reverse is neccesary, not sure why - - const results = langCodes.sort((a, b) => { - if (languagesOrder.indexOf(b) !== -1 || languagesOrder.indexOf(a) !== -1) - return languagesOrder.indexOf(b) - languagesOrder.indexOf(a); - - return a.localeCompare(b); - }); - - return results; -} diff --git a/vite.config.mts b/vite.config.mts index 3d18e359..c77a27c3 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -25,7 +25,7 @@ export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd()); return { plugins: [ - million.vite({ auto: true }), + million.vite({ auto: true, mute: true }), handlebars({ vars: { opensearchEnabled: env.VITE_OPENSEARCH_ENABLED === "true", From 97bd63ca3975d80b6306773112c70bf2f0fd7333 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 20:14:18 +0100 Subject: [PATCH 03/35] Add documentation on languages --- src/assets/README.md | 9 +++++++++ src/assets/languages.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/assets/README.md diff --git a/src/assets/README.md b/src/assets/README.md new file mode 100644 index 00000000..73cd96f0 --- /dev/null +++ b/src/assets/README.md @@ -0,0 +1,9 @@ +# About the languages + +Locales are difficult, here is some guidance. + +## Process on adding new languages +1. Use weblate to add translations, see contributing guidelines. +2. Add your language to `@/assets/languages.ts`. Must be iso format. For joke languages, use any format. +3. If you language doesn't have a region specified. Add a default region in `@/utils/language.ts` at `defaultLanguageCodes` +4. If the flag in the language dropdown doesn't match the correct one. Add a default country in `@/utils/language.ts` at `countryPriority`. diff --git a/src/assets/languages.ts b/src/assets/languages.ts index baa36531..8738aaf0 100644 --- a/src/assets/languages.ts +++ b/src/assets/languages.ts @@ -46,7 +46,7 @@ export const locales = { et, tok, hi, - pt: ptbr, + "pt-BR": ptbr, uk, }; export type Locales = keyof typeof locales; From 68c0444771ad1a37f91ae380e6ac34bc8e2c634d Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 20:29:33 +0100 Subject: [PATCH 04/35] Write declaration file for new lib --- src/@types/country-language.d.ts | 21 +++++++++++++++++++++ src/utils/language.ts | 32 +++++++------------------------- 2 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 src/@types/country-language.d.ts diff --git a/src/@types/country-language.d.ts b/src/@types/country-language.d.ts new file mode 100644 index 00000000..c0badd74 --- /dev/null +++ b/src/@types/country-language.d.ts @@ -0,0 +1,21 @@ +declare module "@ladjs/country-language" { + export interface LanguageObj { + countries: Array<{ + code_2: string; + code_3: string; + numCode: string; + }>; + direction: "RTL" | "LTR"; + name: string[]; + nativeName: string[]; + iso639_1: string; + } + + type Callback = (err: null | string, result: null | T) => void; + + declare namespace lib { + function getLanguage(locale: string, cb: Callback): void; + } + + export = lib; +} diff --git a/src/utils/language.ts b/src/utils/language.ts index 420c8d93..bef4e212 100644 --- a/src/utils/language.ts +++ b/src/utils/language.ts @@ -1,4 +1,4 @@ -import countryLanguages from "@ladjs/country-language"; +import countryLanguages, { LanguageObj } from "@ladjs/country-language"; import { getTag } from "@sozialhelden/ietf-language-tags"; const languageOrder = ["en", "hi", "fr", "de", "nl", "pt"]; @@ -48,18 +48,6 @@ export interface LocaleInfo { isRtl?: boolean; } -interface LanguageObj { - countries: Array<{ - code_2: string; - code_3: string; - numCode: string; - }>; - direction: "RTL" | "LTR"; - name: string[]; - nativeName: string[]; - iso639_1: string; -} - const extraLanguages: Record = { pirate: { code: "pirate", @@ -136,12 +124,9 @@ export function getCountryCodeForLocale(locale: string): string | null { const tag = getTag(locale, true); if (!tag?.language?.Subtag) return null; // this function isnt async, so its garuanteed to work like this - countryLanguages.getLanguage( - tag.language.Subtag, - (_err: string, lang: LanguageObj) => { - if (lang) output = lang; - }, - ); + countryLanguages.getLanguage(tag.language.Subtag, (_err, lang) => { + if (lang) output = lang; + }); if (!output) return null; if (output.countries.length === 0) return null; const priority = countryPriority[output.iso639_1.toLowerCase()]; @@ -169,12 +154,9 @@ export function getLocaleInfo(locale: string): LocaleInfo | null { let output: LanguageObj | null = null as any as LanguageObj; // this function isnt async, so its garuanteed to work like this - countryLanguages.getLanguage( - tag.language.Subtag, - (_err: string, lang: LanguageObj) => { - if (lang) output = lang; - }, - ); + countryLanguages.getLanguage(tag.language.Subtag, (_err, lang) => { + if (lang) output = lang; + }); if (!output) return null; return { From a4f75a34abc6437f0290e3b041227dd0b4fead38 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 20:43:28 +0100 Subject: [PATCH 05/35] Bundling --- vite.config.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vite.config.mts b/vite.config.mts index c77a27c3..264871b0 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -124,8 +124,8 @@ export default defineConfig(({ mode }) => { rollupOptions: { output: { manualChunks(id: string) { - if (id.includes("@sozialhelden+ietf-language-tags")) { - return "ietf-language-tags"; + if (id.includes("@sozialhelden+ietf-language-tags") || id.includes("country-language")) { + return "language-db"; } if (id.includes("hls.js")) { return "hls"; From c3cf9ecf1ac9f4e089d25522e0104381de19916f Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 20:53:07 +0100 Subject: [PATCH 06/35] Update src/assets/README.md Co-authored-by: William Oldham --- src/assets/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/README.md b/src/assets/README.md index 73cd96f0..e8c04d52 100644 --- a/src/assets/README.md +++ b/src/assets/README.md @@ -4,6 +4,6 @@ Locales are difficult, here is some guidance. ## Process on adding new languages 1. Use weblate to add translations, see contributing guidelines. -2. Add your language to `@/assets/languages.ts`. Must be iso format. For joke languages, use any format. -3. If you language doesn't have a region specified. Add a default region in `@/utils/language.ts` at `defaultLanguageCodes` +2. Add your language to `@/assets/languages.ts`. Must be in ISO format (ISO-639 for language and ISO-3166 for country/region). For joke languages, use any format. +3. If your language doesn't have a region specified (Such as in `pt-BR`, `BR` being the region). Add a default region in `@/utils/language.ts` at `defaultLanguageCodes` 4. If the flag in the language dropdown doesn't match the correct one. Add a default country in `@/utils/language.ts` at `countryPriority`. From 7155ffce407eedc168023002cff9148681a048f7 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 21:20:57 +0100 Subject: [PATCH 07/35] Add missing languages + add support for scripts (pain) --- src/assets/languages.ts | 24 ++++++++++++++++++++++++ src/components/FlagIcon.tsx | 2 +- src/utils/language.ts | 23 +++++++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/assets/languages.ts b/src/assets/languages.ts index 8738aaf0..bb195f49 100644 --- a/src/assets/languages.ts +++ b/src/assets/languages.ts @@ -1,13 +1,21 @@ import ar from "@/assets/locales/ar.json"; +import bg from "@/assets/locales/bg.json"; +import bn from "@/assets/locales/bn.json"; import cs from "@/assets/locales/cs.json"; import de from "@/assets/locales/de.json"; +import el from "@/assets/locales/el.json"; import en from "@/assets/locales/en.json"; import es from "@/assets/locales/es.json"; import et from "@/assets/locales/et.json"; +import fa from "@/assets/locales/fa.json"; import fr from "@/assets/locales/fr.json"; +import gu from "@/assets/locales/gu.json"; import he from "@/assets/locales/he.json"; import hi from "@/assets/locales/hi.json"; +import id from "@/assets/locales/id.json"; import it from "@/assets/locales/it.json"; +import ja from "@/assets/locales/ja.json"; +import ko from "@/assets/locales/ko.json"; import lv from "@/assets/locales/lv.json"; import minion from "@/assets/locales/minion.json"; import ne from "@/assets/locales/ne.json"; @@ -15,12 +23,16 @@ import nl from "@/assets/locales/nl.json"; import pirate from "@/assets/locales/pirate.json"; import pl from "@/assets/locales/pl.json"; import ptbr from "@/assets/locales/pt-BR.json"; +import ru from "@/assets/locales/ru.json"; +import sl from "@/assets/locales/sl.json"; import sv from "@/assets/locales/sv.json"; +import ta from "@/assets/locales/ta.json"; import th from "@/assets/locales/th.json"; import tok from "@/assets/locales/tok.json"; import tr from "@/assets/locales/tr.json"; import uk from "@/assets/locales/uk.json"; import vi from "@/assets/locales/vi.json"; +import zhhant from "@/assets/locales/zh-Hant.json"; import zh from "@/assets/locales/zh.json"; export const locales = { @@ -48,5 +60,17 @@ export const locales = { hi, "pt-BR": ptbr, uk, + bg, + bn, + el, + fa, + gu, + id, + ja, + ko, + sl, + ta, + "zh-HANT": zhhant, + ru, }; export type Locales = keyof typeof locales; diff --git a/src/components/FlagIcon.tsx b/src/components/FlagIcon.tsx index 96f9431e..fbc3dae2 100644 --- a/src/components/FlagIcon.tsx +++ b/src/components/FlagIcon.tsx @@ -43,7 +43,7 @@ export function FlagIcon(props: FlagIconProps) { return ( = { ar: "sa", es: "es", zh: "cn", + ko: "kr", + ta: "lk", }; // list of iso639_1 Alpha-2 codes used as default languages @@ -39,6 +41,17 @@ const defaultLanguageCodes: string[] = [ "ar-SA", "es-ES", "et-EE", + "bg-BG", + "bn-BD", + "el-GR", + "fa-IR", + "gu-IN", + "id-ID", + "ja-JP", + "ko-KR", + "sl-SI", + "ta-LK", + "ru-RU", ]; export interface LocaleInfo { @@ -80,7 +93,6 @@ function populateLanguageCode(language: string): string { */ export function getPrettyLanguageNameFromLocale(locale: string): string | null { const tag = getTag(populateLanguageCode(locale), true); - const lang = tag?.language?.Description?.[0] ?? null; if (!lang) return null; @@ -159,12 +171,15 @@ export function getLocaleInfo(locale: string): LocaleInfo | null { }); if (!output) return null; + const extras = []; + if (tag.region?.Description) extras.push(tag.region.Description[0]); + if (tag.script?.Description) extras.push(tag.script.Description[0]); + const extraStringified = extras.map((v) => `(${v})`).join(" "); + return { code: tag.parts.langtag ?? realLocale, isRtl: output.direction === "RTL", - name: - output.name[0] + - (tag.region?.Description ? ` (${tag.region.Description[0]})` : ""), + name: output.name[0] + (extraStringified ? ` ${extraStringified}` : ""), nativeName: output.nativeName[0] ?? undefined, }; } From ddc4119ce594f34f763b4f668730a84f673986b8 Mon Sep 17 00:00:00 2001 From: Joran Date: Tue, 2 Jan 2024 23:34:35 +0000 Subject: [PATCH 08/35] Added translation using Weblate (Romanian) Author: Joran --- src/assets/locales/ro.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/locales/ro.json diff --git a/src/assets/locales/ro.json b/src/assets/locales/ro.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/assets/locales/ro.json @@ -0,0 +1 @@ +{} From 69afb5513f3a4fc078c0b5f4c6f2adfd0c142b7d Mon Sep 17 00:00:00 2001 From: Joran Date: Tue, 2 Jan 2024 23:32:25 +0000 Subject: [PATCH 09/35] Translated using Weblate (French) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/fr/ Author: Joran --- src/assets/locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/fr.json b/src/assets/locales/fr.json index 10a279a0..b3df317c 100644 --- a/src/assets/locales/fr.json +++ b/src/assets/locales/fr.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "Erreur de chargement de la saison", "loadingList": "Chargement...", - "loadingTitle": "Chargement..." + "loadingTitle": "Chargement...", + "unairedEpisodes": "Un ou plusieurs épisodes de cette saison ont été désactivés car ils n'ont pas encore été diffusés." }, "playback": { "speedLabel": "Vitesse de lecture", From 4982afce954d68a43f9e3c9e6ca7551dfe7a3982 Mon Sep 17 00:00:00 2001 From: blikje Date: Tue, 2 Jan 2024 23:11:30 +0000 Subject: [PATCH 10/35] Translated using Weblate (Dutch) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/nl/ Author: blikje --- src/assets/locales/nl.json | 65 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/assets/locales/nl.json b/src/assets/locales/nl.json index 6370aec7..5fc8b49d 100644 --- a/src/assets/locales/nl.json +++ b/src/assets/locales/nl.json @@ -206,7 +206,8 @@ "episodeBadge": "A{{episode}}", "loadingError": "Er ging iets mis bij het laden van dit seizoen", "loadingList": "Aan het laden...", - "loadingTitle": "Aan het zoeken..." + "loadingTitle": "Aan het zoeken...", + "unairedEpisodes": "Een of meer afleveringen van dit seizoen zijn uitgeschakeld omdat ze nog niet zijn uitgezonden." }, "playback": { "speedLabel": "Afspeelsnelheid", @@ -361,8 +362,66 @@ }, "register": { "cta": "Aan de slag", - "text": "Deel de voortgang van je film tussen apparaten en houd ze gesynchroniseerd." + "text": "Deel uw kijkvoortgang tussen apparaten en houd ze gesynchroniseerd.", + "title": "Synchroniseren met de cloud" + }, + "title": "Account" + }, + "appearance": { + "activeTheme": "Actief", + "themes": { + "blue": "Blauw", + "default": "Standaard", + "gray": "Grijs", + "red": "Rood", + "teal": "Groenblauw" + }, + "title": "Uiterlijk" + }, + "connections": { + "server": { + "description": "Als je verbinding wilt maken met een eigen backend om je gegevens op te slaan, schakel dan deze optie in en geef de URL op.", + "label": "Eigen server", + "urlLabel": "Eigen server URL" + }, + "title": "Verbindingen", + "workers": { + "addButton": "Nieuwe worker toevoegen", + "description": "Om de applicatie te laten werken, wordt al het verkeer omgeleid via proxies. Schakel dit in als je je eigen workers wilt gebruiken.", + "emptyState": "Nog geen workers, voeg er hieronder een toe", + "label": "Eigen proxy werker gebruiken", + "urlLabel": "Worker URLs", + "urlPlaceholder": "https://" } - } + }, + "locale": { + "language": "Applicatietaal", + "languageDescription": "Taal wordt toegepast op de hele applicatie.", + "title": "Lokaal" + }, + "reset": "Resetten", + "save": "Wijzigingen opslaan", + "sidebar": { + "info": { + "appVersion": "App versie", + "backendUrl": "Backend URL", + "backendVersion": "Backend versie", + "hostname": "hostnaam", + "insecure": "Onveilig", + "notLoggedIn": "U bent niet ingelogd", + "secure": "Veilig", + "title": "App informatie", + "unknownVersion": "Onbekend", + "userId": "Gebruiker ID" + } + }, + "subtitles": { + "backgroundLabel": "Achtergrond dekking", + "colorLabel": "Kleur", + "previewQuote": "Ik mag niet bang zijn. Angst doodt de geest.", + "textSizeLabel": "Tekengrootte", + "title": "Ondertiteling" + }, + "unsaved": "U heeft niet-opgeslagen wijzigingen" } } From c8a5972f5a974d1d12ab44a9ef4f25679ba766e4 Mon Sep 17 00:00:00 2001 From: Joran Date: Tue, 2 Jan 2024 23:49:51 +0000 Subject: [PATCH 11/35] Translated using Weblate (Romanian) Currently translated at 27.0% (68 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ro/ Author: Joran --- src/assets/locales/ro.json | 121 ++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/ro.json b/src/assets/locales/ro.json index 0967ef42..920b8b4e 100644 --- a/src/assets/locales/ro.json +++ b/src/assets/locales/ro.json @@ -1 +1,120 @@ -{} +{ + "about": { + "description": "movie-web este o aplicație web care caută fluxuri pe internet. Echipa urmărește o abordare mai ales minimalistă a consumului de conținut.", + "faqTitle": "Intrebari obisnuite", + "q1": { + "body": "movie-web nu găzduiește niciun conținut. Când faceți clic pe ceva pentru a viziona, pe Internet este căutat media selectată (Pe ecranul de încărcare și în fila „Surse video”, puteți vedea ce sursă utilizați). Media nu este niciodată încărcată de movie-web, totul se face prin acest mecanism de căutare.", + "title": "De unde vine conținutul?" + }, + "q2": { + "body": "Nu este posibil să solicitați o emisiune sau un film, movie-web nu gestionează niciun conținut. Tot conținutul este vizualizat prin surse de internet.", + "title": "Unde pot solicita o emisiune sau un film?" + }, + "q3": { + "body": "Rezultatele căutării noastre sunt furnizate de The Movie Database (TMDB) și afișați indiferent dacă sursele noastre au de fapt conținutul.", + "title": "Rezultatele căutării afișează emisiunea sau filmul, de ce nu îl pot reda?" + }, + "title": "Despre movie-web" + }, + "actions": { + "copied": "Copiat", + "copy": "Copie" + }, + "auth": { + "createAccount": "Nu aveți încă un cont? <0>Creați un cont.", + "deviceNameLabel": "Nume dispozitiv", + "deviceNamePlaceholder": "Telefon personal", + "generate": { + "description": "Fraza de acces acționează ca nume de utilizator și parolă. Asigurați-vă că îl păstrați în siguranță, deoarece va trebui să îl introduceți pentru a vă conecta la contul dvs", + "next": "Mi-am salvat expresia de acces", + "passphraseFrameLabel": "Fraza de acces", + "title": "Fraza dvs. de acces" + }, + "hasAccount": "ai deja un cont? <0>Autentificați-vă aici.", + "login": { + "description": "Vă rugăm să introduceți fraza de acces pentru a vă conecta la contul dvs", + "deviceLengthError": "Introduceți un nume de dispozitiv", + "passphraseLabel": "Expresie de acces din 12 cuvinte", + "passphrasePlaceholder": "Fraza de acces", + "submit": "Log in", + "title": "conecteaza-te la contul tau", + "validationError": "Fraza de acces incorectă sau incompletă" + }, + "register": { + "information": { + "color1": "Culoarea profilului unu", + "color2": "Culoarea profilului doi", + "header": "Introduceți un nume pentru dispozitivul dvs. și alegeți culorile și o pictogramă de utilizator la alegerea dvs", + "icon": "Pictograma utilizator", + "next": "Următorul", + "title": "Informatii despre cont" + } + }, + "trust": { + "failed": { + "text": "L-ai configurat corect?", + "title": "Nu s-a putut ajunge la server" + }, + "host": "Vă conectați la <0>{{hostname}} - vă rugăm să confirmați că aveți încredere înainte de a vă crea un cont", + "no": "Întoarce-te", + "title": "Ai încredere în acest server?", + "yes": "Am încredere în acest server" + }, + "verify": { + "description": "Introduceți expresia de acces de mai devreme pentru a confirma că ați salvat-o și pentru a vă crea contul", + "invalidData": "Datele nu sunt valide", + "noMatch": "Fraza de acces nu se potrivește", + "passphraseLabel": "Fraza dvs. de acces de 12 cuvinte", + "recaptchaFailed": "Validarea ReCaptcha a eșuat", + "register": "Creează cont", + "title": "Confirmați-vă fraza de acces" + } + }, + "errors": { + "badge": "S-a spart", + "details": "Detalii despre eroare", + "reloadPage": "Reîncărcați pagina", + "showError": "Afișați detalii despre eroare", + "title": "Am intampinat o eroare!" + }, + "footer": { + "legal": { + "disclaimer": "Disclaimer", + "disclaimerText": "movie-web nu găzduiește niciun fișier, ci doar trimite la servicii terțe. Problemele juridice ar trebui abordate cu gazdele și furnizorii de fișiere. movie-web nu este responsabil pentru niciun fișier media afișat de furnizorii de video." + }, + "links": { + "discord": "Discord", + "dmca": "DMCA", + "github": "GitHub" + }, + "tagline": "Urmăriți emisiunile și filmele preferate cu această aplicație de streaming open source." + }, + "global": { + "name": "movie-web", + "pages": { + "about": "Despre", + "dmca": "DMCA", + "login": "Log in", + "pagetitle": "{{title}} - movie-web", + "register": "Inregistreaza-te", + "settings": "Setări" + } + }, + "home": { + "bookmarks": { + "sectionTitle": "Marcaje" + }, + "continueWatching": { + "sectionTitle": "Continuați vizionarea" + }, + "mediaList": { + "stopEditing": "Opriți editarea" + }, + "search": { + "allResults": "Asta e tot ce avem!" + } + }, + "settings": { + "unsaved": "Aveți modificări nesalvate" + } +} From d4ef78bef399282d086d418d2ee81d1c5d38e63a Mon Sep 17 00:00:00 2001 From: Fluffy Date: Wed, 3 Jan 2024 01:20:13 +0000 Subject: [PATCH 12/35] Added translation using Weblate (Punjabi) Author: Fluffy --- src/assets/locales/pa.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/locales/pa.json diff --git a/src/assets/locales/pa.json b/src/assets/locales/pa.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/assets/locales/pa.json @@ -0,0 +1 @@ +{} From 495dc3e6e27044a0625133e5d47e819e80a73665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Horv=C3=A1th?= Date: Wed, 3 Jan 2024 03:17:57 +0000 Subject: [PATCH 13/35] Added translation using Weblate (Hungarian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Author: Dávid Horváth --- src/assets/locales/hu.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/locales/hu.json diff --git a/src/assets/locales/hu.json b/src/assets/locales/hu.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/assets/locales/hu.json @@ -0,0 +1 @@ +{} From 9f6f04f32724c48e6e81f90b8e2aa7d5dbd19a8d Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 16:26:29 +0000 Subject: [PATCH 14/35] Added translation using Weblate (Galician) Author: n1ck --- src/assets/locales/gl.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/locales/gl.json diff --git a/src/assets/locales/gl.json b/src/assets/locales/gl.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/assets/locales/gl.json @@ -0,0 +1 @@ +{} From 10443e1b82e696c2c7885d6167352dbbb7ab2157 Mon Sep 17 00:00:00 2001 From: 5Litt <5Litt@users.noreply.weblate.movie-web.app> Date: Wed, 3 Jan 2024 08:42:21 +0000 Subject: [PATCH 15/35] Translated using Weblate (Czech) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/cs/ Author: 5Litt <5Litt@users.noreply.weblate.movie-web.app> --- src/assets/locales/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/cs.json b/src/assets/locales/cs.json index 3de3f251..8ca19cd9 100644 --- a/src/assets/locales/cs.json +++ b/src/assets/locales/cs.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "Chyba při načítání sezóny", "loadingList": "Načítání...", - "loadingTitle": "Načítání..." + "loadingTitle": "Načítání...", + "unairedEpisodes": "Jedna nebo více epizod v této sezóně nejsou dostupné, protože ještě nebyly odvysílány." }, "playback": { "speedLabel": "Rychlost přehrávání", From e59f3a5417d039ee243ddb1cdf43d8122d867e3b Mon Sep 17 00:00:00 2001 From: Isra Date: Wed, 3 Jan 2024 12:34:57 +0000 Subject: [PATCH 16/35] Translated using Weblate (German) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/de/ Author: Isra --- src/assets/locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/de.json b/src/assets/locales/de.json index 1b7facca..1c3b56b5 100644 --- a/src/assets/locales/de.json +++ b/src/assets/locales/de.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "Fehler beim Laden der Sitzung", "loadingList": "Wird geladen...", - "loadingTitle": "Wird geladen..." + "loadingTitle": "Wird geladen...", + "unairedEpisodes": "Eine oder mehrere Episoden dieser Staffel wurden deaktiviert, weil sie noch nicht ausgestrahlt wurden." }, "playback": { "speedLabel": "Wiedergabegeschwindigkeit", From bdd7a368f27a7596abe6b1bc043ccca9407c9c78 Mon Sep 17 00:00:00 2001 From: Aristeo Ibarra Date: Wed, 3 Jan 2024 15:48:58 +0000 Subject: [PATCH 17/35] Translated using Weblate (Spanish) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/es/ Author: Aristeo Ibarra --- src/assets/locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/es.json b/src/assets/locales/es.json index 4e92ce61..9322681a 100644 --- a/src/assets/locales/es.json +++ b/src/assets/locales/es.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "Error al cargar la temporada", "loadingList": "Cargando...", - "loadingTitle": "Cargando..." + "loadingTitle": "Cargando...", + "unairedEpisodes": "Uno o más episodios de esta temporada se han desactivado porque aún no se han emitido." }, "playback": { "speedLabel": "Velocidad de reproducción", From 51f60e864f48e485fbc4e575ac431da846ff145c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Jan 2024 12:50:40 +0000 Subject: [PATCH 18/35] Translated using Weblate (Russian) Currently translated at 39.8% (100 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ru/ Author: Alex --- src/assets/locales/ru.json | 64 +++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/ru.json b/src/assets/locales/ru.json index 39495597..d92c3c9d 100644 --- a/src/assets/locales/ru.json +++ b/src/assets/locales/ru.json @@ -1,6 +1,15 @@ { "about": { + "description": "movie-web - это веб-приложение, которое ищет в интернете потоки. Команда стремится к минималистичному подходу к потреблению контента.", "faqTitle": "Общие вопросы", + "q1": { + "body": "movie-web не размещает у себя никакого контента. Когда вы нажимаете на что-то для просмотра, в интернете происходит поиск выбранного медиа файла (на экране загрузки и во вкладке \"Видео источники\" вы можете увидеть, какой источник вы используете). Медиа файлы никогда не загружается на movie-web, все происходит через этот механизм поиска.", + "title": "Откуда берётся контент?" + }, + "q2": { + "body": "Невозможно запросить сериал или фильм, movie-web не управляет никаким контентом. Весь контент просматривается через источники в интернете.", + "title": "Где я могу запросить показ сериала или фильма?" + }, "title": "О movie-web" }, "actions": { @@ -12,6 +21,23 @@ "deviceNameLabel": "Имя устройства", "deviceNamePlaceholder": "Личный телефон", "hasAccount": "У вас уже есть аккаунт? <0>Войдите здесь.", + "login": { + "deviceLengthError": "Введите имя устройства", + "passphraseLabel": "12-словная парольная фраза", + "submit": "Авторизоваться", + "title": "Войдите в свой аккаунт", + "validationError": "Неверная или неполная парольная фраза" + }, + "register": { + "information": { + "color1": "Цвет профиля один", + "color2": "Цвет профиля два", + "header": "Введите название устройства, выберите цвета и значок пользователя по своему усмотрению", + "icon": "Значок пользователя", + "next": "Далее", + "title": "Информация об аккаунте" + } + }, "trust": { "host": "Вы подключаетесь к <0>{{hostname}} - пожалуйста, подтвердите, что вы доверяете ему, прежде чем создавать учётную запись", "no": "Вернуться назад", @@ -23,14 +49,20 @@ } }, "footer": { + "legal": { + "disclaimer": "Отказ от ответственности", + "disclaimerText": "movie-web не размещает никаких файлов, а лишь предоставляет ссылки на сторонние сервисы. Юридические вопросы следует решать с владельцами файлов и поставщиками услуг. movie-web не несёт ответственности за любые медиа файлы, показанные поставщиками видео." + }, "links": { - "discord": "Дискорд", + "discord": "Discord", "dmca": "DMCA", "github": "GitHub" } }, "global": { + "name": "movie-web", "pages": { + "about": "О", "dmca": "DMCA", "settings": "Настройки" } @@ -66,6 +98,9 @@ "quality": { "automaticLabel": "Автоматическое качество" }, + "settings": { + "sourceItem": "Видео источники" + }, "subtitles": { "title": "Субтитры" } @@ -73,6 +108,32 @@ }, "settings": { "account": { + "accountDetails": { + "logoutButton": "Выйти" + }, + "actions": { + "delete": { + "button": "Удалить аккаунт", + "confirmButton": "Удалить аккаунт", + "confirmDescription": "Вы уверены, что хотите удалить свой аккаунт? Все ваши данные будут потеряны!", + "confirmTitle": "Вы уверены?", + "text": "Это действие необратимо. Все данные будут удалены, и восстановить их будет невозможно.", + "title": "Удалить аккаунт" + }, + "title": "Действия" + }, + "devices": { + "deviceNameLabel": "Имя устройства", + "removeDevice": "Удалить", + "title": "Устройства" + }, + "profile": { + "finish": "Завершить редактирование", + "firstColor": "Цвет профиля один", + "secondColor": "Цвет профиля два", + "title": "Редактирование изображения профиля", + "userIcon": "Значок пользователя" + }, "register": { "title": "Синхронизировать с облаком" }, @@ -95,6 +156,7 @@ "label": "Пользовательский сервер", "urlLabel": "URL-адрес пользовательского сервера" }, + "title": "Соединения", "workers": { "addButton": "Добавить новый прокси-сервер", "description": "Для работы приложения весь трафик маршрутизируется через прокси. Включите это, если вы хотите использовать свои собственных прокси-серверы.", From 2dd3da747fe1dd5efb4214a8190e9fe7b7dd116d Mon Sep 17 00:00:00 2001 From: Kartavya Patel Date: Wed, 3 Jan 2024 05:02:11 +0000 Subject: [PATCH 19/35] Translated using Weblate (Hindi) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/hi/ Author: Kartavya Patel --- src/assets/locales/hi.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/assets/locales/hi.json b/src/assets/locales/hi.json index 43895072..220f90fa 100644 --- a/src/assets/locales/hi.json +++ b/src/assets/locales/hi.json @@ -155,7 +155,7 @@ "donation": "दान करें", "logout": "लॉग आउट", "register": "क्लाउड से सिंक करें", - "settings": "चेड चाड करे", + "settings": "सेटिंग्स", "support": "सहायता" } }, @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "सीज़न लोड करने में त्रुटि", "loadingList": "लोड हो रहा है..।", - "loadingTitle": "लोड हो रहा है..।" + "loadingTitle": "लोड हो रहा है..।", + "unairedEpisodes": "इस सीज़न में एक या अधिक एपिसोड अक्षम कर दिए गए हैं क्योंकि वे अभी तक प्रसारित नहीं हुए हैं।" }, "playback": { "speedLabel": "प्लेबैक गति", From 8bf0e75a9ff7472b499debccd6ddf5c2fc760a2d Mon Sep 17 00:00:00 2001 From: Kartavya Patel Date: Wed, 3 Jan 2024 05:01:00 +0000 Subject: [PATCH 20/35] Translated using Weblate (Gujarati) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/gu/ Author: Kartavya Patel --- src/assets/locales/gu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/gu.json b/src/assets/locales/gu.json index d3f6e722..b3195453 100644 --- a/src/assets/locales/gu.json +++ b/src/assets/locales/gu.json @@ -206,7 +206,8 @@ "episodeBadge": "એપિસોડ{{episode}}", "loadingError": "સીઝન લોડ કરવામાં ભૂલ", "loadingList": "લોડ થાય છે...", - "loadingTitle": "લોડ થાય છે..." + "loadingTitle": "લોડ થાય છે...", + "unairedEpisodes": "આ સિઝનમાં એક અથવા વધુ એપિસોડ અક્ષમ કરવામાં આવ્યા છે કારણ કે તે હજુ સુધી પ્રસારિત થયા નથી." }, "playback": { "speedLabel": "પ્લેબેક ઝડપ", From dd20b22c792311cdd978486af6186a39668e0dc6 Mon Sep 17 00:00:00 2001 From: Willy Billy Date: Wed, 3 Jan 2024 07:14:30 +0000 Subject: [PATCH 21/35] Translated using Weblate (Romanian) Currently translated at 46.6% (117 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ro/ Author: Willy Billy --- src/assets/locales/ro.json | 98 +++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/ro.json b/src/assets/locales/ro.json index 920b8b4e..0bb8dd2d 100644 --- a/src/assets/locales/ro.json +++ b/src/assets/locales/ro.json @@ -111,7 +111,103 @@ "stopEditing": "Opriți editarea" }, "search": { - "allResults": "Asta e tot ce avem!" + "allResults": "Asta e tot ce avem!", + "failed": "Găsire media eșuată, încearcă din nou!", + "loading": "Se încarcă...", + "noResults": "Nu am putut găsi nimic!", + "placeholder": "La ce dorești să te uiți?", + "sectionTitle": "Rezultate de căutare" + }, + "titles": { + "day": { + "default": "La ce vrei să te uiți după-amiaza asta?", + "extra": [ + "Te simți aventuros? Jurassic Park ar putea fi o alegere perfectă." + ] + }, + "morning": { + "default": "La ce dorești să te in uiți dimineață aceasta?", + "extra": [ + "Aud că Before Sunrise este bun" + ] + }, + "night": { + "default": "La ce dorești să te uiți în astă seară?", + "extra": [ + "Obosit? Aud că The Exorcist is good." + ] + } + } + }, + "media": { + "episodeDisplay": "S{{season}} E{{episode}}", + "types": { + "movie": "Film", + "show": "Spectacol" + } + }, + "navigation": { + "banner": { + "offline": "Verificați-vă conexiunea de internet" + }, + "menu": { + "about": "Despre noi", + "donation": "Donează", + "logout": "Deconectați-vă", + "register": "Sincronizare în cloud", + "settings": "Setări", + "support": "Ajutor" + } + }, + "notFound": { + "badge": "Nu a fost găsit", + "goHome": "Înapoi acasă", + "message": "Ne-am uitat peste tot: sub pubele, în dulap, În spatele proxy-ului dar din păcate nu am găsit pagina pe care dumneavoastră o căutați.", + "title": "N-am putut găsi pagina" + }, + "overlays": { + "close": "Închide" + }, + "player": { + "back": { + "default": "Înapoi acasă", + "short": "Înapoi" + }, + "casting": { + "enabled": "Casting pe dispozitiv..." + }, + "menus": { + "episodes": { + "button": "Episoade", + "emptyState": "Nu sunt episoade in sezonul acesta, reveniți mai târziu!", + "episodeBadge": "E{{episode}}", + "loadingError": "Eroare la încărcarea sezonul", + "loadingList": "Se încarcă...", + "loadingTitle": "Se încarcă...", + "unairedEpisodes": "Unul sau mai multe episoade din sezonul acesta sunt indisponibile deoarece incă nu au venit încă." + }, + "settings": { + "downloadItem": "Descarcă", + "enableSubtitles": "Activează subtitlurile", + "experienceSection": "Experiență de vizionare", + "playbackItem": "Setări de redare", + "qualityItem": "Calitate", + "sourceItem": "Surse video", + "subtitleItem": "Setările subtitlului", + "videoSection": "Setări video" + }, + "sources": { + "noEmbeds": { + "text": "Nu am putut găsi nicio incorporare, vă rog să încercați o altă sursă.", + "title": "Nu a fost găsită nicio încorporare" + }, + "noStream": { + "text": "Sursa asta nu are nicio sursă de streaming pentru filmul său spectacolul.", + "title": "Niciun stream" + }, + "title": "Surse", + "unknownOption": "Necunoscut" + } } }, "settings": { From 0d21bb4e2c844ac1c67e86f507664fc543db6b8c Mon Sep 17 00:00:00 2001 From: Fluffy Date: Wed, 3 Jan 2024 01:32:19 +0000 Subject: [PATCH 22/35] Translated using Weblate (Punjabi) Currently translated at 1.5% (4 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/pa/ Author: Fluffy --- src/assets/locales/pa.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/pa.json b/src/assets/locales/pa.json index 0967ef42..1919b92f 100644 --- a/src/assets/locales/pa.json +++ b/src/assets/locales/pa.json @@ -1 +1,18 @@ -{} +{ + "actions": { + "copy": "ਕਾਪੀ" + }, + "auth": { + "login": { + "submit": "ਲੌਗ-ਇਨ" + }, + "register": { + "information": { + "next": "ਅਗਲਾ" + } + }, + "trust": { + "no": "ਵਾਪਸ ਜਾਓ" + } + } +} From ea295e769d562e0c94c16176503886ed9b14077e Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 16:46:18 +0000 Subject: [PATCH 23/35] Translated using Weblate (Galician) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/gl/ Author: n1ck --- src/assets/locales/gl.json | 428 ++++++++++++++++++++++++++++++++++++- 1 file changed, 427 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/gl.json b/src/assets/locales/gl.json index 0967ef42..d5ab264e 100644 --- a/src/assets/locales/gl.json +++ b/src/assets/locales/gl.json @@ -1 +1,427 @@ -{} +{ + "about": { + "description": "movie-web é unha aplicación web que busca transmisións na rede. O equipo ten como obxectivo manter un enfoque principalmente minimalista para consumir os contidos.", + "faqTitle": "Preguntas frecuentes", + "q1": { + "body": "movie-web non aloxa ningún contido. Cando premes en algo para ver o contenido, búscase en internet o medio seleccionado. (Na pantalla de carga e na lapela 'fontes de video' podes ver que fonte se está a empregar. O contido nunca se carga en movie-web, todo realízase a través deste método de busca.", + "title": "De onde proveñen os contidos?" + }, + "q2": { + "body": "Non é posible solicitar unha película. movie-web non xestiona ningún contido. Todo o contido é xestionado a través de fontes na rede.", + "title": "Onde poido solicitar unha película a engadir?" + }, + "q3": { + "body": "Os nosos resultados de busqueda proveñen de The Movie Database (TMDB) e se mostran independentemente de se as nosas fontes multimedia teñen realmente o contido.", + "title": "Os resultados da busca mostran a serie ou película... Por qué non poido reproducila?" + }, + "title": "Acerca de movie-web" + }, + "actions": { + "copied": "Copiado", + "copy": "Copiar" + }, + "auth": { + "createAccount": "Non tes unha conta aínda? <0>Crea unha conta.", + "deviceNameLabel": "Nome do dispositivo", + "deviceNamePlaceholder": "Teléfono persoal", + "generate": { + "description": "A túa contraseña actua como o teu nome de usuario e contraseña. Asegúrate de mantelas seguras, xa que as necesitarás para iniciar sesión na túa conta", + "next": "Gardei a contraseña exitosamente", + "passphraseFrameLabel": "Contraseña", + "title": "A túa contraseña" + }, + "hasAccount": "Tes xa unha conta? <0>Inicia sesión aquí.", + "login": { + "description": "Por favor, ingresa a túa contraseña para iniciar sesión na túa conta", + "deviceLengthError": "Por favor, ingresa un nome de dispositivo", + "passphraseLabel": "Contraseña de 12 caracteres", + "passphrasePlaceholder": "Contraseña", + "submit": "Iniciar sesión", + "title": "Inicia sesión na túa conta", + "validationError": "Contraseña incorrecta ou incompleta" + }, + "register": { + "information": { + "color1": "Cór de perfil un", + "color2": "Cór de perfil dous", + "header": "Ingresa un nome para o teu dispositivo, elixe cores, e un icono de usuario", + "icon": "Ícono de usuario", + "next": "Seguinte", + "title": "Información da conta" + } + }, + "trust": { + "failed": { + "text": "Configurachelo correctamente?", + "title": "Non se puido conectar ao servidor" + }, + "host": "Estaste a conectar a <0>{{hostname}} - por favor, confirma se confías antes de crear a conta", + "no": "Regresar", + "title": "Confías neste servidor?", + "yes": "Si, si que confío neste servidor" + }, + "verify": { + "description": "Por favor, ingresa a túa contraseña para confirmar que está gardada para crear a túa conta", + "invalidData": "Os datos non son válidos", + "noMatch": "A contraseña non coincide", + "passphraseLabel": "A contraseña debe de ser de 12 caracteres", + "recaptchaFailed": "A validación ReCaptcha fallou", + "register": "Crear conta", + "title": "Confirma a túa contraseña" + } + }, + "errors": { + "badge": "Rompeu", + "details": "Detalles do erro", + "reloadPage": "Recargar a páxina", + "showError": "Mostrar detalles do erro", + "title": "Atopamos un erro!" + }, + "footer": { + "legal": { + "disclaimer": "Descargo de responsabilidade", + "disclaimerText": "movie-web non aloxa ningún arquivo, simplemente enlaza con servizos de terceiros. Os problemas legais deben ser tratados cós proovedores de arquivos e servizos. movie-web non se fai responsable dos arquivos multimedia mostrados polos provedores de video." + }, + "links": { + "discord": "Discord", + "dmca": "DMCA", + "github": "GitHub" + }, + "tagline": "Disfruta das túas series e películas favoritas con esta aplicación de transmisión de código aberto." + }, + "global": { + "name": "movie-web", + "pages": { + "about": "Acerca de", + "dmca": "DMCA", + "login": "Iniciar sesión", + "pagetitle": "{{title}} - movie-web", + "register": "Rexistrarse", + "settings": "Configuración" + } + }, + "home": { + "bookmarks": { + "sectionTitle": "Marcadores" + }, + "continueWatching": { + "sectionTitle": "Continuar vendo" + }, + "mediaList": { + "stopEditing": "Deter edición" + }, + "search": { + "allResults": "Esto é todo o que temos!", + "failed": "Error ao encontrar contido... intentao de novo!", + "loading": "Cargando...", + "noResults": "Non atopamos nada!", + "placeholder": "Que che gustaría ver?", + "sectionTitle": "Resultados da busca" + }, + "titles": { + "day": { + "default": "Que che gustaría ver esta tarde?", + "extra": [ + "Sínteste aventureiro? Jurassic Park podería ser a elección perfecta." + ] + }, + "morning": { + "default": "Que che gustaría ver esta mañá?", + "extra": [ + "Escoitei que “Antes del amanecer” é boa" + ] + }, + "night": { + "default": "Que che gustaría ver esta noite?", + "extra": [ + "Canso? Escoitei que “El Exorcista” é boa." + ] + } + } + }, + "media": { + "episodeDisplay": "T{{season}} E{{episode}}", + "types": { + "movie": "Película", + "show": "Serie" + } + }, + "navigation": { + "banner": { + "offline": "Verifica a túa conexión a internet" + }, + "menu": { + "about": "Acerca de nós", + "donation": "Doar", + "logout": "Cerrar sesión", + "register": "Sincronizar coa nube", + "settings": "Configuración", + "support": "Soporte" + } + }, + "notFound": { + "badge": "Non atopado", + "goHome": "Volver ao inicio", + "message": "Prometocho, buscamos en todas partes: debaixo dos contenedores, no armario, detrás do proxy, pero ao final non puidemos atopar a páxina que estabas buscando.", + "title": "Non atopei a páxona que estabas a buscar" + }, + "overlays": { + "close": "Cerrar" + }, + "player": { + "back": { + "default": "Volver ao inicio", + "short": "Volver" + }, + "casting": { + "enabled": "Transmitiendo ao dispositivo..." + }, + "menus": { + "downloads": { + "disclaimer": "As descargas proveñen do provedor. movie-web non ten control sobre as descargas e a súa procedencia.", + "downloadPlaylist": "Descargar lista", + "downloadSubtitle": "Descargar subtítulos actuais", + "downloadVideo": "Descargar video", + "hlsDisclaimer": "As descargas realizanse directamente dende o proovedor. movie-web non ten control sobre como se xestionan as descargas. Ten en conta que estás a descargar unha lista de reproducción HLS, dirixidos a usuarios familiarizados coa transmisión multimedia avanzada.", + "onAndroid": { + "1": "Para descargar en Android, fai click no botón de descarga e despois, na nova páxina, mantén presionadoo vídeo e selecciona gardar.", + "shortTitle": "Descargar / Android", + "title": "Descargando en Android" + }, + "onIos": { + "1": "Para descargar en iOS, fai clic no botón de descarga e despois, na nova páxina, fai click en , e despois Gardar en archivos .", + "shortTitle": "Descargar / iOS", + "title": "Descargando en iOS" + }, + "onPc": { + "1": "Nunha PC, fai click no botón de descargas e despois, na nova páxina, fai click dereito no video e selecciona Gardar vídeo como...", + "shortTitle": "Descargar / PC", + "title": "Descargando en PC" + }, + "title": "Descargar" + }, + "episodes": { + "button": "Episodios", + "emptyState": "Non hai episodios nesta temporada, Intentao máis tarde!", + "episodeBadge": "E{{episode}}", + "loadingError": "Error cargando a sesión", + "loadingList": "Cargando...", + "loadingTitle": "Cargando...", + "unairedEpisodes": "Un ou máis episodios nesta temporada foron desactivados porque non sairon aínda." + }, + "playback": { + "speedLabel": "Velocidade de reproducción", + "title": "Configuración de reproducción" + }, + "quality": { + "automaticLabel": "Calidade automática", + "hint": "Podes intentar <0>cambiar de fonte para obter diferentes opcións de calidade.", + "iosNoQuality": "Debido a limitacións definidas por Apple, a selección de calidade no está disponible en iOS para esta fonte. Podes intentar <0>cambiar a outra fonte para obter diferentes opcións de calidade.", + "title": "Calidade" + }, + "settings": { + "downloadItem": "Descargar", + "enableSubtitles": "Activar subtítulos", + "experienceSection": "Configuración de experiencia", + "playbackItem": "Configuración do playback", + "qualityItem": "Calidade", + "sourceItem": "Fonte do video", + "subtitleItem": "Configuración dos subtítulos", + "videoSection": "Configuración de video" + }, + "sources": { + "failed": { + "text": "Acaba de producirse un erro ao intentar atopar videos, por favor, intenta cunha fonte distinta.", + "title": "Erro ao retirar" + }, + "noEmbeds": { + "text": "Non puidemos atopar ningún embed, por favor, intenta cunha fonte diferente.", + "title": "No se atoparon embeds" + }, + "noStream": { + "text": "Nesta fonte non hai contidos sobre esta película ou episodio.", + "title": "Sin fonte" + }, + "title": "Fontes", + "unknownOption": "Descoñecido" + }, + "subtitles": { + "customChoice": "Seleccionar subtítulos dende o arquivo", + "customizeLabel": "Personalizar", + "offChoice": "Apagar", + "settings": { + "backlink": "Subtítulos personalizados", + "delay": "Retardo dos subtítulos", + "fixCapitals": "Arreglar capitalización" + }, + "title": "Subtítulos", + "unknownLanguage": "Descoñecido" + } + }, + "metadata": { + "failed": { + "badge": "Erro", + "homeButton": "Ir ao inicio", + "text": "Non se puideron cargar os metadatos do contido de TMDB. Por favor, verifica se TMDB está caído ou bloqueado na túa conexión a internet.", + "title": "Error ao cargar os metadatos" + }, + "notFound": { + "badge": "Non atopado", + "homeButton": "Volver ao inicio", + "text": "Non puidemos encontrar o contenido que solicitache. Xa seña que se eliminara ou modificara a URL.", + "title": "No se pudo atopar ese contenido." + } + }, + "nextEpisode": { + "cancel": "Cancelar", + "next": "Seguinte episodio" + }, + "playbackError": { + "badge": "Error de reproducción", + "errors": { + "errorAborted": "A obtención do contido foi cancelada pola solicitude do usuario.", + "errorDecode": "A pesar de ser determinado previamente como utilizable, produciuse un erro ao intentar decodificar o recurso do contido, o que resultou nun erro.", + "errorGenericMedia": "Produxose un erro descoñecido no contido.", + "errorNetwork": "Produxose un erro de rede que impidideu obter o contido de maneira exitosa, a pesar de estar disponible anteriormente.", + "errorNotSupported": "O contido ou o proovedor do contido non é compatible." + }, + "homeButton": "Ir ao inicio", + "text": "Produxose un erro ao intentar reproducir o contenido. Por favor, inténtao de novo.", + "title": "Non se puido reproducir o video!" + }, + "scraping": { + "items": { + "failure": "Ocurreu un erro", + "notFound": "Non ten o video", + "pending": "Verificando vídeos..." + }, + "notFound": { + "badge": "Non atopado", + "detailsButton": "Mostrar detalles", + "homeButton": "Ir ao inicio", + "text": "Buscamos nos nosos proovedores e non puidemos atopar o contido que estás a buscar. Nós, non aloxamos o contido e non temos control sobre o que está dispoñible. Fai click en 'Mostrar detalles' a continuación para obter máis información.", + "title": "Non puidemos atopar eso" + } + }, + "time": { + "regular": "{{timeWatched}} / {{duration}}", + "remaining": "{{timeLeft}} restante • Finaliza ás {{timeFinished, datetime}}", + "shortRegular": "{{timeWatched}}", + "shortRemaining": "-{{timeLeft}}" + } + }, + "screens": { + "dmca": { + "text": "Benvido/a á páxona de contacto DMA de movie-web! Respetamos os dereitos de propiedade intelectual e queremos abordar calqueiro problema de dereitos de autor de maneira más rápida. Se crees que o teu traballo con dereitos de autor está sendo empregado incorrectamente na nosa plataforma, envñia un aviso DMCA detallado ao correo electrónico que se mostra a continuación. Inclue unha descripción do material con dereitos de autor, os seus datos de contacto e unha declaración de boa fé. Estamos comprometidos a resolver estos asuntos o máis rápido posible e agradecemos a túa cooperación para manter a movie-web como un lugar que respeta a creatividade e os dereitos de autor.", + "title": "DMCA" + }, + "loadingApp": "Cargando aplicación", + "loadingUser": "Cargando o teu perfil", + "loadingUserError": { + "logout": "Pechar sesión", + "reset": "Reiniciar servidor personalizado", + "text": "Erro ao cargar o teu perfil", + "textWithReset": "Erro ao cargar o teu perfil dende o teu servidor personalizado, queres reiniciar e volver ao servidor por defecto?" + }, + "migration": { + "failed": "Erro ao migrar os teus datos.", + "inProgress": "Porfavor, espera mientras migramos tus datos. Esto no debería llevar mucho." + } + }, + "settings": { + "account": { + "accountDetails": { + "deviceNameLabel": "Nome do dispositivo", + "deviceNamePlaceholder": "Teléfono persoal", + "editProfile": "Editar", + "logoutButton": "Pechar sesión" + }, + "actions": { + "delete": { + "button": "Eliminar conta", + "confirmButton": "Eliminar conta", + "confirmDescription": "Estas seguro/a que queres eliminar a túa conta? Todos os datos serán eliminados!", + "confirmTitle": "Estás seguro/a?", + "text": "Esta acción é irreversible. Todos os datos serán eliminados e nada poderá ser recuperado.", + "title": "Eliminar conta" + }, + "title": "Accións" + }, + "devices": { + "deviceNameLabel": "Nome do dispositivo", + "failed": "Erro ao cargar sesións", + "removeDevice": "Quitar", + "title": "Dispositivos" + }, + "profile": { + "finish": "Acabar de editar", + "firstColor": "Cór de perfil un", + "secondColor": "Cór de perfil dous", + "title": "Editar foto de perfil", + "userIcon": "Icono de usuario" + }, + "register": { + "cta": "Empezar", + "text": "Compartir o teu progreso entre dispositivos e mantelos sincronizados.", + "title": "Sincronizar á nube" + }, + "title": "Conta" + }, + "appearance": { + "activeTheme": "Activo", + "themes": { + "blue": "Azul", + "default": "Por defecto", + "gray": "Gris", + "red": "Vermello", + "teal": "Turquesa" + }, + "title": "Apariencia" + }, + "connections": { + "server": { + "description": "Se che gustaría conectar un servidor personalizado de backend para almacenar os teus datos, activa esto e indica a URL.", + "label": "Servidor personalizado", + "urlLabel": "Servidor personalizado URL" + }, + "title": "Conexións", + "workers": { + "addButton": "Añadir novo", + "description": "Para facer que a aplicación funcione, todo o tráfico é organizado en proxies. Activa esta opción se queres empregar os teus propios workers.", + "emptyState": "Non hai workers aínda, engade un abaixo", + "label": "Usar proxy workers personalizados", + "urlLabel": "URLs dos workers", + "urlPlaceholder": "https://" + } + }, + "locale": { + "language": "Lingua da aplicación", + "languageDescription": "Lingua empregada en toda aplicación.", + "title": "Local" + }, + "reset": "Reinicio", + "save": "Gardar", + "sidebar": { + "info": { + "appVersion": "Versión da aplicación", + "backendUrl": "URL do Backend", + "backendVersion": "Versión do Backend", + "hostname": "Nome do Host (Hostname)", + "insecure": "Non seguro", + "notLoggedIn": "Non iniciache sesión", + "secure": "Seguro", + "title": "Información da aplicación", + "unknownVersion": "Descoñecido", + "userId": "ID do usuario" + } + }, + "subtitles": { + "backgroundLabel": "Opacidade do fondo", + "colorLabel": "Cór", + "previewQuote": "Non debo temer. O medo é o asasino da mente.", + "textSizeLabel": "Tamaño da fonte", + "title": "Subtítulos" + }, + "unsaved": "Tes cambios sen gardar" + } +} From 10912533922d808fe0b52f307651fd00f407c837 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 3 Jan 2024 23:54:08 +0100 Subject: [PATCH 24/35] Handle more turnstile errors + show interactive prompt + handle provider api metadata errors Co-authored-by: Jip Frijlink --- src/assets/locales/en.json | 10 +++ src/components/overlays/OverlayDisplay.tsx | 37 ++++++++- src/pages/parts/player/MetaPart.tsx | 28 ++++++- src/pages/parts/player/ScrapingPart.tsx | 29 ++++++- src/stores/banner/index.ts | 8 ++ src/stores/turnstile/index.tsx | 94 ++++++++++++++++------ 6 files changed, 178 insertions(+), 28 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 61decb79..9329ec0e 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -165,6 +165,12 @@ "close": "Close" }, "player": { + "turnstile": { + "verifyingHumanity": "Verifying your humanity...", + "title": "We need to verify that you're human.", + "description": "Please verify that you are human by completing the Captcha on the right. This is to keep movie-web safe!", + "error": "Failed to verify your humanity. Please try again." + }, "back": { "default": "Back to home", "short": "Back" @@ -261,6 +267,10 @@ "text": "Could not load the media's metadata from TMDB. Please check whether TMDB is down or blocked on your internet connection.", "title": "Failed to load metadata" }, + "api": { + "text": "Could not load API metadata, please check your internet connection.", + "title": "Failed to load API metadata" + }, "notFound": { "badge": "Not found", "homeButton": "Back to home", diff --git a/src/components/overlays/OverlayDisplay.tsx b/src/components/overlays/OverlayDisplay.tsx index 1898a92f..048ec0e7 100644 --- a/src/components/overlays/OverlayDisplay.tsx +++ b/src/components/overlays/OverlayDisplay.tsx @@ -2,12 +2,14 @@ import classNames from "classnames"; import FocusTrap from "focus-trap-react"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; +import { useTranslation } from "react-i18next"; import { Transition } from "@/components/utils/Transition"; import { useInternalOverlayRouter, useRouterAnchorUpdate, } from "@/hooks/useOverlayRouter"; +import { TurnstileProvider } from "@/stores/turnstile"; export interface OverlayProps { id: string; @@ -15,6 +17,34 @@ export interface OverlayProps { darken?: boolean; } +function TurnstileInteractive() { + const { t } = useTranslation(); + const [show, setShow] = useState(false); + + // this may not rerender with different dom structure, must be exactly the same always + return ( +
+
+
+

+ {t("player.turnstile.title")} +

+

{t("player.turnstile.description")}

+
+ setShow(shouldShow)} + /> +
+
+ ); +} + export function OverlayDisplay(props: { children: ReactNode }) { const router = useInternalOverlayRouter("hello world :)"); const refRouter = useRef(router); @@ -27,7 +57,12 @@ export function OverlayDisplay(props: { children: ReactNode }) { r.close(); }; }, []); - return
{props.children}
; + return ( +
+ + {props.children} +
+ ); } export function OverlayPortal(props: { diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx index 1ea6cd7e..4930fffb 100644 --- a/src/pages/parts/player/MetaPart.tsx +++ b/src/pages/parts/player/MetaPart.tsx @@ -43,7 +43,11 @@ export function MetaPart(props: MetaPartProps) { const { error, value, loading } = useAsync(async () => { const providerApiUrl = getLoadbalancedProviderApiUrl(); if (providerApiUrl) { - await fetchMetadata(providerApiUrl); + try { + await fetchMetadata(providerApiUrl); + } catch (err) { + throw new Error("failed-api-metadata"); + } } else { setCachedMetadata([ ...providers.listSources(), @@ -117,6 +121,28 @@ export function MetaPart(props: MetaPartProps) { ); } + if (error && error.message === "failed-api-metadata") { + return ( + + + + {t("player.metadata.failed.badge")} + + {t("player.metadata.api.text")} + {t("player.metadata.api.title")} + + + + ); + } + if (error) { return ( diff --git a/src/pages/parts/player/ScrapingPart.tsx b/src/pages/parts/player/ScrapingPart.tsx index 6687d3b1..eb0e5bfc 100644 --- a/src/pages/parts/player/ScrapingPart.tsx +++ b/src/pages/parts/player/ScrapingPart.tsx @@ -1,6 +1,7 @@ import { ProviderControls, ScrapeMedia } from "@movie-web/providers"; import classNames from "classnames"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useMountedState } from "react-use"; import type { AsyncReturnType } from "type-fest"; @@ -8,6 +9,8 @@ import { scrapePartsToProviderMetric, useReportProviders, } from "@/backend/helpers/report"; +import { Icon, Icons } from "@/components/Icon"; +import { Loading } from "@/components/layout/Loading"; import { ScrapeCard, ScrapeItem, @@ -18,6 +21,7 @@ import { useListCenter, useScrape, } from "@/hooks/useProviderScrape"; +import { LargeTextPart } from "@/pages/parts/util/LargeTextPart"; export interface ScrapingProps { media: ScrapeMedia; @@ -32,9 +36,11 @@ export function ScrapingPart(props: ScrapingProps) { const { report } = useReportProviders(); const { startScraping, sourceOrder, sources, currentSource } = useScrape(); const isMounted = useMountedState(); + const { t } = useTranslation(); const containerRef = useRef(null); const listRef = useRef(null); + const [failedStartScrape, setFailedStartScrape] = useState(false); const renderedOnce = useListCenter( containerRef, listRef, @@ -72,7 +78,7 @@ export function ScrapingPart(props: ScrapingProps) { ), ); props.onGetStream?.(output); - })(); + })().catch(() => setFailedStartScrape(true)); }, [startScraping, props, report, isMounted]); let currentProviderIndex = sourceOrder.findIndex( @@ -81,11 +87,28 @@ export function ScrapingPart(props: ScrapingProps) { if (currentProviderIndex === -1) currentProviderIndex = sourceOrder.length - 1; + if (failedStartScrape) + return ( + + } + > + {t("player.turnstile.error")} + + ); + return (
+ {!sourceOrder || sourceOrder.length === 0 ? ( +
+ +

{t("player.turnstile.verifyingHumanity")}

+
+ ) : null}
{ const source = sources[order.id]; const distance = Math.abs( - sourceOrder.findIndex((t) => t.id === order.id) - + sourceOrder.findIndex((o) => o.id === order.id) - currentProviderIndex, ); return ( diff --git a/src/stores/banner/index.ts b/src/stores/banner/index.ts index f8173ec2..22df9fc2 100644 --- a/src/stores/banner/index.ts +++ b/src/stores/banner/index.ts @@ -11,24 +11,32 @@ interface BannerInstance { interface BannerStore { banners: BannerInstance[]; isOnline: boolean; + isTurnstile: boolean; location: string | null; updateHeight(id: string, height: number): void; showBanner(id: string): void; hideBanner(id: string): void; setLocation(loc: string | null): void; updateOnline(isOnline: boolean): void; + updateTurnstile(isTurnstile: boolean): void; } export const useBannerStore = create( immer((set) => ({ banners: [], isOnline: true, + isTurnstile: false, location: null, updateOnline(isOnline) { set((s) => { s.isOnline = isOnline; }); }, + updateTurnstile(isTurnstile) { + set((s) => { + s.isTurnstile = isTurnstile; + }); + }, setLocation(loc) { set((s) => { s.location = loc; diff --git a/src/stores/turnstile/index.tsx b/src/stores/turnstile/index.tsx index b421b70b..72586c73 100644 --- a/src/stores/turnstile/index.tsx +++ b/src/stores/turnstile/index.tsx @@ -1,3 +1,5 @@ +import classNames from "classnames"; +import { useRef } from "react"; import Turnstile, { BoundTurnstileObject } from "react-turnstile"; import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; @@ -6,19 +8,31 @@ import { reportCaptchaSolve } from "@/backend/helpers/report"; import { conf } from "@/setup/config"; export interface TurnstileStore { - turnstile: BoundTurnstileObject | null; + isInWidget: boolean; + turnstiles: { + controls: BoundTurnstileObject; + isInPopout: boolean; + id: string; + }[]; cbs: ((token: string | null) => void)[]; - setTurnstile(v: BoundTurnstileObject | null): void; + setTurnstile( + id: string, + v: BoundTurnstileObject | null, + isInPopout: boolean, + ): void; getToken(): Promise; - processToken(token: string | null): void; + processToken(token: string | null, widgetId: string): void; } export const useTurnstileStore = create( immer((set, get) => ({ - turnstile: null, + isInWidget: false, + turnstiles: [], cbs: [], - processToken(token) { + processToken(token, widgetId) { const cbs = get().cbs; + const turnstile = get().turnstiles.find((v) => v.id === widgetId); + if (turnstile?.id !== widgetId) return; cbs.forEach((fn) => fn(token)); set((s) => { s.cbs = []; @@ -37,16 +51,26 @@ export const useTurnstileStore = create( }); }); }, - setTurnstile(v) { + setTurnstile(id, controls, isInPopout) { set((s) => { - s.turnstile = v; + s.turnstiles = s.turnstiles.filter((v) => v.id !== id); + if (controls) { + s.turnstiles.push({ + controls, + isInPopout, + id, + }); + } }); }, })), ); export function getTurnstile() { - return useTurnstileStore.getState().turnstile; + const turnstiles = useTurnstileStore.getState().turnstiles; + const inPopout = turnstiles.find((v) => v.isInPopout); + if (inPopout) return inPopout; + return turnstiles[0]; } export function isTurnstileInitialized() { @@ -55,9 +79,12 @@ export function isTurnstileInitialized() { export async function getTurnstileToken() { const turnstile = getTurnstile(); - turnstile?.reset(); - turnstile?.execute(); try { + // I hate turnstile + (window as any).turnstile.execute( + document.querySelector(`#${turnstile.id}`), + {}, + ); const token = await useTurnstileStore.getState().getToken(); reportCaptchaSolve(true); return token; @@ -67,23 +94,44 @@ export async function getTurnstileToken() { } } -export function TurnstileProvider() { +export function TurnstileProvider(props: { + isInPopout?: boolean; + onUpdateShow?: (show: boolean) => void; +}) { const siteKey = conf().TURNSTILE_KEY; + const idRef = useRef(null); const setTurnstile = useTurnstileStore((s) => s.setTurnstile); const processToken = useTurnstileStore((s) => s.processToken); if (!siteKey) return null; return ( - { - setTurnstile(bound); - }} - onError={() => { - processToken(null); - }} - onVerify={(token) => { - processToken(token); - }} - /> +
+ { + idRef.current = widgetId; + setTurnstile(widgetId, bound, !!props.isInPopout); + }} + onError={() => { + const id = idRef.current; + if (!id) return; + processToken(null, id); + }} + onVerify={(token) => { + const id = idRef.current; + if (!id) return; + processToken(token, id); + props.onUpdateShow?.(false); + }} + onBeforeInteractive={() => { + props.onUpdateShow?.(true); + }} + refreshExpired="never" + execution="render" + /> +
); } From f41629f60d1a2eed9a9b5abd850e9a2ce4b96b07 Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 20:48:19 +0000 Subject: [PATCH 25/35] Translated using Weblate (Chinese (Simplified)) Currently translated at 99.6% (250 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/zh_Hans/ Author: n1ck --- src/assets/locales/zh.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/zh.json b/src/assets/locales/zh.json index 4c6b8924..bbe75a48 100644 --- a/src/assets/locales/zh.json +++ b/src/assets/locales/zh.json @@ -206,7 +206,8 @@ "episodeBadge": "第{{episode}}集", "loadingError": "加载分季时发生错误", "loadingList": "载入中……", - "loadingTitle": "载入中……" + "loadingTitle": "载入中……", + "unairedEpisodes": "本季中的一集或多集已因尚未播出而被禁用。" }, "playback": { "speedLabel": "播放速度", From 3a6380e626c1427c6d72a1b34d3bfe3fafbff87f Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 20:47:33 +0000 Subject: [PATCH 26/35] Translated using Weblate (Arabic) Currently translated at 100.0% (251 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ar/ Author: n1ck --- src/assets/locales/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/ar.json b/src/assets/locales/ar.json index 7c2ea3b3..0fc20948 100644 --- a/src/assets/locales/ar.json +++ b/src/assets/locales/ar.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "خطأ في تحميل الموسم", "loadingList": "تحميل...", - "loadingTitle": "تحميل..." + "loadingTitle": "تحميل...", + "unairedEpisodes": "تم تعطيل حلقة واحدة أو أكثر من هذا الموسم لأنه لم يتم بثها بعد." }, "playback": { "speedLabel": "سرعة التشغيل", From 7ac19b70d103a847c321bfe41c63f7ec1f0ec175 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Jan 2024 22:08:44 +0000 Subject: [PATCH 27/35] Translated using Weblate (Russian) Currently translated at 47.4% (119 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ru/ Author: Alex --- src/assets/locales/ru.json | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/assets/locales/ru.json b/src/assets/locales/ru.json index d92c3c9d..7d02aa92 100644 --- a/src/assets/locales/ru.json +++ b/src/assets/locales/ru.json @@ -10,6 +10,10 @@ "body": "Невозможно запросить сериал или фильм, movie-web не управляет никаким контентом. Весь контент просматривается через источники в интернете.", "title": "Где я могу запросить показ сериала или фильма?" }, + "q3": { + "body": "Наши результаты поиска основаны на базе данных The Movie Database (TMDB) и отображаются вне зависимости от того, есть ли в наших источниках соответствующий контент.", + "title": "В результатах поиска отображается сериал или фильм, но почему я не могу воспроизвести его?" + }, "title": "О movie-web" }, "actions": { @@ -57,7 +61,8 @@ "discord": "Discord", "dmca": "DMCA", "github": "GitHub" - } + }, + "tagline": "Смотрите любимые сериалы и фильмы с помощью этого приложения для потокового вещания с открытым исходным кодом." }, "global": { "name": "movie-web", @@ -78,10 +83,21 @@ "loading": "Загрузка..." } }, + "media": { + "episodeDisplay": "С{{season}} Э{{episode}}", + "types": { + "movie": "Фильм", + "show": "Сериал" + } + }, "navigation": { + "banner": { + "offline": "Проверьте подключение к Интернету" + }, "menu": { "about": "О нас", "donation": "Пожертвовать", + "logout": "Выйти", "settings": "Настройки", "support": "Поддержка" } @@ -95,11 +111,26 @@ "downloadSubtitle": "Скачать текущие субтитры", "title": "Скачать" }, + "episodes": { + "button": "Эпизоды", + "loadingError": "Ошибка при загрузке сезона", + "loadingList": "Загрузка...", + "loadingTitle": "Загрузка..." + }, + "playback": { + "speedLabel": "Скорость воспроизведения", + "title": "Настройки воспроизведения" + }, "quality": { "automaticLabel": "Автоматическое качество" }, "settings": { - "sourceItem": "Видео источники" + "downloadItem": "Скачать", + "playbackItem": "Настройки воспроизведения", + "qualityItem": "Качество", + "sourceItem": "Видео источники", + "subtitleItem": "Настройки субтитров", + "videoSection": "Настройки видео" }, "subtitles": { "title": "Субтитры" From 35e1c809a9dcf32f1a3b4ebc7a05c4ea3be9dafc Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 3 Jan 2024 22:59:27 +0000 Subject: [PATCH 28/35] Deleted translation using Weblate (Hungarian) Author: admin --- src/assets/locales/hu.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/assets/locales/hu.json diff --git a/src/assets/locales/hu.json b/src/assets/locales/hu.json deleted file mode 100644 index 0967ef42..00000000 --- a/src/assets/locales/hu.json +++ /dev/null @@ -1 +0,0 @@ -{} From a1962aeecf72caecc38fc086bb24f9cc4a7b5e92 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Wed, 3 Jan 2024 23:14:26 +0000 Subject: [PATCH 29/35] Add ro, gl, pa to languages, fix flag logic when countries doesn't exist in tag --- src/assets/languages.ts | 6 ++++++ src/utils/language.ts | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/assets/languages.ts b/src/assets/languages.ts index bb195f49..b5900847 100644 --- a/src/assets/languages.ts +++ b/src/assets/languages.ts @@ -9,6 +9,7 @@ import es from "@/assets/locales/es.json"; import et from "@/assets/locales/et.json"; import fa from "@/assets/locales/fa.json"; import fr from "@/assets/locales/fr.json"; +import gl from "@/assets/locales/gl.json"; import gu from "@/assets/locales/gu.json"; import he from "@/assets/locales/he.json"; import hi from "@/assets/locales/hi.json"; @@ -20,9 +21,11 @@ import lv from "@/assets/locales/lv.json"; import minion from "@/assets/locales/minion.json"; import ne from "@/assets/locales/ne.json"; import nl from "@/assets/locales/nl.json"; +import pa from "@/assets/locales/pa.json"; import pirate from "@/assets/locales/pirate.json"; import pl from "@/assets/locales/pl.json"; import ptbr from "@/assets/locales/pt-BR.json"; +import ro from "@/assets/locales/ro.json"; import ru from "@/assets/locales/ru.json"; import sl from "@/assets/locales/sl.json"; import sv from "@/assets/locales/sv.json"; @@ -72,5 +75,8 @@ export const locales = { ta, "zh-HANT": zhhant, ru, + gl, + pa, + ro, }; export type Locales = keyof typeof locales; diff --git a/src/utils/language.ts b/src/utils/language.ts index 53ff10b0..f9f47250 100644 --- a/src/utils/language.ts +++ b/src/utils/language.ts @@ -18,6 +18,7 @@ const countryPriority: Record = { zh: "cn", ko: "kr", ta: "lk", + gl: "es", }; // list of iso639_1 Alpha-2 codes used as default languages @@ -52,6 +53,7 @@ const defaultLanguageCodes: string[] = [ "sl-SI", "ta-LK", "ru-RU", + "gl-ES", ]; export interface LocaleInfo { @@ -134,14 +136,17 @@ export function sortLangCodes(langCodes: string[]) { export function getCountryCodeForLocale(locale: string): string | null { let output: LanguageObj | null = null as any as LanguageObj; const tag = getTag(locale, true); + if (!tag?.language?.Subtag) return null; // this function isnt async, so its garuanteed to work like this countryLanguages.getLanguage(tag.language.Subtag, (_err, lang) => { if (lang) output = lang; }); if (!output) return null; - if (output.countries.length === 0) return null; const priority = countryPriority[output.iso639_1.toLowerCase()]; + if (output.countries.length === 0) { + return priority ?? null; + } if (priority) { const priotizedCountry = output.countries.find( (v) => v.code_2.toLowerCase() === priority, From 7cc515c7abad27d9eb6874681b093a1f5e50fb09 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Wed, 3 Jan 2024 23:14:45 +0000 Subject: [PATCH 30/35] Bump Version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c3e1b2d8..39f158a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "movie-web", - "version": "4.2.2", + "version": "4.2.3", "private": true, "homepage": "https://movie-web.app", "scripts": { From 117566fa7c5ee615a068a6f498a6e424aa8f96aa Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Jan 2024 23:01:44 +0000 Subject: [PATCH 31/35] Translated using Weblate (Russian) Currently translated at 47.8% (120 of 251 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ru/ Author: Alex --- src/assets/locales/ru.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/assets/locales/ru.json b/src/assets/locales/ru.json index 7d02aa92..057cbd28 100644 --- a/src/assets/locales/ru.json +++ b/src/assets/locales/ru.json @@ -135,6 +135,9 @@ "subtitles": { "title": "Субтитры" } + }, + "playbackError": { + "badge": "Ошибка воспроизведения" } }, "settings": { From 9eac31662e56606606354a7d888277b7754e3075 Mon Sep 17 00:00:00 2001 From: chaos Date: Wed, 3 Jan 2024 23:05:00 +0000 Subject: [PATCH 32/35] Translated using Weblate (Estonian) Currently translated at 100.0% (257 of 257 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/et/ Author: chaos --- src/assets/locales/et.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/et.json b/src/assets/locales/et.json index 8d316497..0663d58d 100644 --- a/src/assets/locales/et.json +++ b/src/assets/locales/et.json @@ -206,7 +206,8 @@ "episodeBadge": "E{{episode}}", "loadingError": "Hooaja laadimine ebaōnnestus", "loadingList": "Laadimine...", - "loadingTitle": "Laadimine..." + "loadingTitle": "Laadimine...", + "unairedEpisodes": "Üks või mitu selle hooaja episoodi on välja lülitatud, sest neid ei ole veel eetris olnud." }, "playback": { "speedLabel": "Taasesituse kiirus", @@ -258,6 +259,10 @@ } }, "metadata": { + "api": { + "text": "API metaandmete laadimine ebaõnnestus, palun kontrollige oma internetiühendust.", + "title": "API metaandmete laadimine ebaõnnestus" + }, "failed": { "badge": "Ebaōnnestus", "homeButton": "Mine koju", @@ -307,6 +312,12 @@ "remaining": "{{timeLeft}} alles • Lõppeb {{timeFinished, datetime}}", "shortRegular": "{{timeWatched}}", "shortRemaining": "-{{timeLeft}}" + }, + "turnstile": { + "description": "Palun kinnitage, et olete inimene, täites paremal asuva Captcha. See on selleks, et hoida movie-web turvalisena!", + "error": "Ei õnnestunud kontrollida teie inimlikkust. Palun proovige uuesti.", + "title": "Me peame kontrollima, et te olete inimene.", + "verifyingHumanity": "Kontrollime kas olete robot..." } }, "screens": { From 2d6745a574bc4507f3756bdcfd7fd37faa1eddb7 Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 23:10:15 +0000 Subject: [PATCH 33/35] Translated using Weblate (Spanish) Currently translated at 100.0% (257 of 257 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/es/ Author: n1ck --- src/assets/locales/es.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/assets/locales/es.json b/src/assets/locales/es.json index 9322681a..f051ceb0 100644 --- a/src/assets/locales/es.json +++ b/src/assets/locales/es.json @@ -259,6 +259,10 @@ } }, "metadata": { + "api": { + "text": "No ha sido posible cargar la metadata de la API, por favor, comprueba tu conexión a internet.", + "title": "No ha sido posible cargar los metadatos de la API" + }, "failed": { "badge": "Error", "homeButton": "Ir al inicio", @@ -308,6 +312,12 @@ "remaining": "{{timeLeft}} restante • Finaliza a las {{timeFinished, datetime}}", "shortRegular": "{{timeWatched}}", "shortRemaining": "-{{timeLeft}}" + }, + "turnstile": { + "description": "Por favor, confirma que eres humano completando el Captcha. Esto es para mantener movie-web seguro!", + "error": "Ha habido un error al verificar tu humanidad. Por favor, prueba de nuevo.", + "title": "Necesitamos verificar que eres humano.", + "verifyingHumanity": "Verificando tu hunanidad…" } }, "screens": { From 0403ed235b70709b2e7ad1a98e88704340ab685a Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Jan 2024 23:10:25 +0000 Subject: [PATCH 34/35] Translated using Weblate (Russian) Currently translated at 49.4% (127 of 257 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/ru/ Author: Alex --- src/assets/locales/ru.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/assets/locales/ru.json b/src/assets/locales/ru.json index 057cbd28..e743e8a6 100644 --- a/src/assets/locales/ru.json +++ b/src/assets/locales/ru.json @@ -52,6 +52,10 @@ "register": "Создать учётную запись" } }, + "errors": { + "details": "Подробности ошибки", + "showError": "Показать сведения об ошибке" + }, "footer": { "legal": { "disclaimer": "Отказ от ответственности", @@ -69,6 +73,7 @@ "pages": { "about": "О", "dmca": "DMCA", + "pagetitle": "{{title}} - movie-web", "settings": "Настройки" } }, @@ -80,6 +85,7 @@ "sectionTitle": "Продолжить просмотр" }, "search": { + "allResults": "Это все, что у нас есть!", "loading": "Загрузка..." } }, @@ -137,7 +143,12 @@ } }, "playbackError": { - "badge": "Ошибка воспроизведения" + "badge": "Ошибка воспроизведения", + "errors": { + "errorDecode": "Несмотря на то, что ранее этот медиаресурс был пригодным для использования, при попытке его декодирования произошла ошибка." + }, + "text": "При попытке воспроизвести медиа файл произошла ошибка. Пожалуйста, попробуйте ещё раз.", + "title": "Не удалось воспроизвести видео!" } }, "settings": { From 9d537d34a14204b78308512d2fa552d828927756 Mon Sep 17 00:00:00 2001 From: n1ck Date: Wed, 3 Jan 2024 23:05:08 +0000 Subject: [PATCH 35/35] Translated using Weblate (Galician) Currently translated at 100.0% (257 of 257 strings) Translation: movie-web/website Translate-URL: https://weblate.movie-web.app/projects/movie-web/website/gl/ Author: n1ck --- src/assets/locales/gl.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/assets/locales/gl.json b/src/assets/locales/gl.json index d5ab264e..63fe31fa 100644 --- a/src/assets/locales/gl.json +++ b/src/assets/locales/gl.json @@ -259,6 +259,10 @@ } }, "metadata": { + "api": { + "text": "Non puiden cargar os metadatos da API, por favor, comproba a túa conexión a internet.", + "title": "Non foi posible cargar os metadatos da API" + }, "failed": { "badge": "Erro", "homeButton": "Ir ao inicio", @@ -308,6 +312,12 @@ "remaining": "{{timeLeft}} restante • Finaliza ás {{timeFinished, datetime}}", "shortRegular": "{{timeWatched}}", "shortRemaining": "-{{timeLeft}}" + }, + "turnstile": { + "description": "Por favor, verifica que eres un humán completando o Captcha. Isto é para mantee movie-web seguro!", + "error": "Houbo un erro ao verificar a túa humanidade. Por favor, volve a intentalo.", + "title": "Necesitamos verificar que realmente eres un humán.", + "verifyingHumanity": "Verificando a túa humanidade…" } }, "screens": {