15 changed files with 243 additions and 112 deletions
@ -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}`; |
|
||||||
} |
|
@ -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<string, string> = { |
||||||
|
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<string, LocaleInfo> = { |
||||||
|
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, |
||||||
|
}; |
||||||
|
} |
@ -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; |
|
||||||
} |
|
Loading…
Reference in new issue