2026-01-11 17:18:42 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { ISOLanguage, IsoLanguages } from '@/base/IsoLanguages.ts';
|
2026-01-22 00:12:22 +01:00
|
|
|
import { i18nResources } from '@/i18n';
|
2026-01-11 17:18:42 +01:00
|
|
|
|
|
|
|
|
export type LanguageObject = ISOLanguage & { orgCode: string; isoCode: string };
|
|
|
|
|
|
|
|
|
|
export const DEFAULT_LANGUAGE = 'en';
|
|
|
|
|
|
2026-01-10 19:45:02 +01:00
|
|
|
// source: https://github.com/i18next/i18next/blob/485b4ec8183952b3de8fe5e79dff6467c3afd9d3/src/i18next.js#L561
|
|
|
|
|
const RTL_LANGUAGES = [
|
|
|
|
|
'ar',
|
|
|
|
|
'shu',
|
|
|
|
|
'sqr',
|
|
|
|
|
'ssh',
|
|
|
|
|
'xaa',
|
|
|
|
|
'yhd',
|
|
|
|
|
'yud',
|
|
|
|
|
'aao',
|
|
|
|
|
'abh',
|
|
|
|
|
'abv',
|
|
|
|
|
'acm',
|
|
|
|
|
'acq',
|
|
|
|
|
'acw',
|
|
|
|
|
'acx',
|
|
|
|
|
'acy',
|
|
|
|
|
'adf',
|
|
|
|
|
'ads',
|
|
|
|
|
'aeb',
|
|
|
|
|
'aec',
|
|
|
|
|
'afb',
|
|
|
|
|
'ajp',
|
|
|
|
|
'apc',
|
|
|
|
|
'apd',
|
|
|
|
|
'arb',
|
|
|
|
|
'arq',
|
|
|
|
|
'ars',
|
|
|
|
|
'ary',
|
|
|
|
|
'arz',
|
|
|
|
|
'auz',
|
|
|
|
|
'avl',
|
|
|
|
|
'ayh',
|
|
|
|
|
'ayl',
|
|
|
|
|
'ayn',
|
|
|
|
|
'ayp',
|
|
|
|
|
'bbz',
|
|
|
|
|
'pga',
|
|
|
|
|
'he',
|
|
|
|
|
'iw',
|
|
|
|
|
'ps',
|
|
|
|
|
'pbt',
|
|
|
|
|
'pbu',
|
|
|
|
|
'pst',
|
|
|
|
|
'prp',
|
|
|
|
|
'prd',
|
|
|
|
|
'ug',
|
|
|
|
|
'ur',
|
|
|
|
|
'ydd',
|
|
|
|
|
'yds',
|
|
|
|
|
'yih',
|
|
|
|
|
'ji',
|
|
|
|
|
'yi',
|
|
|
|
|
'hbo',
|
|
|
|
|
'men',
|
|
|
|
|
'xmn',
|
|
|
|
|
'fa',
|
|
|
|
|
'jpr',
|
|
|
|
|
'peo',
|
|
|
|
|
'pes',
|
|
|
|
|
'prs',
|
|
|
|
|
'dv',
|
|
|
|
|
'sam',
|
|
|
|
|
'ckb',
|
|
|
|
|
];
|
|
|
|
|
|
2026-01-11 17:18:42 +01:00
|
|
|
export function getISOLanguageFor(code: string, orgCode: string = code, isoCode: string = code): LanguageObject | null {
|
|
|
|
|
if (IsoLanguages[code]) {
|
|
|
|
|
return {
|
|
|
|
|
...IsoLanguages[code],
|
|
|
|
|
orgCode,
|
|
|
|
|
isoCode,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getISOLanguage(code: string): LanguageObject | null {
|
|
|
|
|
return (
|
|
|
|
|
getISOLanguageFor(code) ??
|
|
|
|
|
getISOLanguageFor(code.toLowerCase(), code) ??
|
|
|
|
|
getISOLanguageFor(code.replace('-', '_'), code) ??
|
|
|
|
|
getISOLanguageFor(code.replace('_', '-'), code) ??
|
|
|
|
|
getISOLanguageFor(code.split('-')[0], code) ??
|
|
|
|
|
getISOLanguageFor(code.split('_')[0], code)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 16:33:37 +01:00
|
|
|
const toUniqueISOLanguageCodes = (codes: string[]): string[] => {
|
2026-01-11 17:18:42 +01:00
|
|
|
const languages = codes.map((code) => getISOLanguage(code)).filter((code) => code !== null);
|
|
|
|
|
const languagesByIsoCode = Object.groupBy(languages, (language) => language.isoCode);
|
|
|
|
|
|
|
|
|
|
return Object.entries(languagesByIsoCode)
|
|
|
|
|
.filter(([, languagesOfIsoCode]) => !!languagesOfIsoCode?.length)
|
|
|
|
|
.map(([, languagesOfIsoCode]) => languagesOfIsoCode![0].orgCode);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function getPreferredISOLanguageCodes(): readonly string[] {
|
|
|
|
|
const preferredLanguages = toUniqueISOLanguageCodes([...navigator.languages]);
|
|
|
|
|
|
|
|
|
|
if (!preferredLanguages.length) {
|
|
|
|
|
return [DEFAULT_LANGUAGE];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return preferredLanguages;
|
|
|
|
|
}
|
2026-01-10 19:45:02 +01:00
|
|
|
|
|
|
|
|
export function getLanguageReadingDirection(code: string): 'ltr' | 'rtl' {
|
|
|
|
|
const language = getISOLanguage(code);
|
|
|
|
|
|
|
|
|
|
if (code.toLowerCase().includes('-latn') || language?.isoCode.includes('-latn')) {
|
|
|
|
|
return 'ltr';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code.toLowerCase().includes('-arab') || language?.isoCode.toLowerCase().includes('-arab')) {
|
|
|
|
|
return 'rtl';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (RTL_LANGUAGES.includes(code) || RTL_LANGUAGES.includes(language?.isoCode as string)) {
|
|
|
|
|
return 'rtl';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'ltr';
|
|
|
|
|
}
|
2026-01-22 00:12:22 +01:00
|
|
|
|
|
|
|
|
export function detectLocale(): string {
|
|
|
|
|
const normalizedLanguageCodes = getPreferredISOLanguageCodes()
|
|
|
|
|
.filter((code) => code !== null)
|
|
|
|
|
.filter((code) => getISOLanguage(code))
|
|
|
|
|
.filter((code) => i18nResources.includes(code as (typeof i18nResources)[number]));
|
|
|
|
|
|
|
|
|
|
return normalizedLanguageCodes[0] ?? DEFAULT_LANGUAGE;
|
|
|
|
|
}
|