Update identity generator language list references (#776)

This commit is contained in:
Leendert de Borst
2026-06-28 21:13:04 +02:00
parent 529a4c7fd7
commit f2159e1263
23 changed files with 342 additions and 819 deletions

View File

@@ -6,7 +6,8 @@ import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
import { getAvailableLanguages, getAvailableAgeRanges, ILanguageOption, IAgeRangeOption } from '@/utils/dist/core/identity-generator';
import { getAvailableLanguages, getAvailableAgeRanges, IAgeRangeOption } from '@/utils/dist/core/identity-generator';
import { getLanguageInfo } from '@/utils/dist/core/models/defaults';
/**
* Identity Generator Settings page component.
@@ -21,7 +22,7 @@ const IdentityGeneratorSettings: React.FC = () => {
const [language, setLanguage] = useState<string>('en');
const [gender, setGender] = useState<string>('random');
const [ageRange, setAgeRange] = useState<string>('random');
const [languageOptions, setLanguageOptions] = useState<ILanguageOption[]>([]);
const [languageOptions, setLanguageOptions] = useState<string[]>([]);
const [ageRangeOptions, setAgeRangeOptions] = useState<IAgeRangeOption[]>([]);
const GENDER_OPTIONS = [
@@ -34,7 +35,9 @@ const IdentityGeneratorSettings: React.FC = () => {
* Load settings on mount.
*/
useEffect(() => {
const languages = getAvailableLanguages();
const languages = getAvailableLanguages().sort(
(a, b) => getLanguageInfo(a).label.localeCompare(getLanguageInfo(b).label)
);
const ranges = getAvailableAgeRanges();
setLanguageOptions(languages);
setAgeRangeOptions(ranges);
@@ -109,11 +112,14 @@ const IdentityGeneratorSettings: React.FC = () => {
onChange={(e) => handleLanguageChange(e.target.value)}
className="w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-gray-900 dark:text-white focus:ring-primary-500 focus:border-primary-500"
>
{languageOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.flag} {option.label}
</option>
))}
{languageOptions.map((code) => {
const info = getLanguageInfo(code);
return (
<option key={code} value={code}>
{info.flag} {info.label}
</option>
);
})}
</select>
</div>
</div>

View File

@@ -3,6 +3,8 @@
* Add new languages here to make them available throughout the application
*/
import { getLanguageInfo } from '@/utils/dist/core/models/defaults';
import daTranslations from './locales/da.json';
import deTranslations from './locales/de.json';
import enTranslations from './locales/en.json';
@@ -76,121 +78,13 @@ export const LANGUAGE_RESOURCES = {
};
/**
* List of all available languages with their code, name, native name and flag.
* When adding a new language, add the language to the map here.
* List of all available UI languages with their code, native name and flag.
*/
export const AVAILABLE_LANGUAGES: ILanguageConfig[] = [
{
code: 'da',
name: 'Danish',
nativeName: 'Dansk',
flag: '🇩🇰'
},
{
code: 'de',
name: 'German',
nativeName: 'Deutsch',
flag: '🇩🇪'
},
{
code: 'en',
name: 'English',
nativeName: 'English',
flag: '🇺🇸'
},
{
code: 'es',
name: 'Spanish',
nativeName: 'Español',
flag: '🇪🇸'
},
{
code: 'fi',
name: 'Finnish',
nativeName: 'Suomi',
flag: '🇫🇮'
},
{
code: 'fr',
name: 'French',
nativeName: 'Français',
flag: '🇫🇷'
},
{
code: 'he',
name: 'Hebrew',
nativeName: 'עברית',
flag: '🇮🇱'
},
{
code: 'it',
name: 'Italian',
nativeName: 'Italiano',
flag: '🇮🇹'
},
{
code: 'nl',
name: 'Dutch',
nativeName: 'Nederlands',
flag: '🇳🇱'
},
{
code: 'pl',
name: 'Polish',
nativeName: 'Polski',
flag: '🇵🇱'
},
{
code: 'pt',
name: 'Portuguese Brazilian',
nativeName: 'Português Brasileiro',
flag: '🇧🇷'
},
{
code: 'ro',
name: 'Romanian',
nativeName: 'Română',
flag: '🇷🇴'
},
{
code: 'ru',
name: 'Russian',
nativeName: 'Русский',
flag: '🇷🇺'
},
{
code: 'sv',
name: 'Swedish',
nativeName: 'Svenska',
flag: '🇸🇪'
},
{
code: 'uk',
name: 'Ukrainian',
nativeName: 'Українська',
flag: '🇺🇦'
},
{
code: 'zh',
name: 'Chinese',
nativeName: '简体中文',
flag: '🇨🇳'
},
/*
* {
* code: 'es',
* name: 'Spanish',
* nativeName: 'Español',
* flag: '🇪🇸'
* },
* {
* code: 'fr',
* name: 'French',
* nativeName: 'Français',
* flag: '🇫🇷'
* },
*/
];
export const AVAILABLE_LANGUAGES: ILanguageConfig[] =
(Object.keys(LANGUAGE_RESOURCES) as Array<keyof typeof LANGUAGE_RESOURCES>).map((code) => {
const info = getLanguageInfo(code);
return { code, nativeName: info.label, flag: info.flag };
});
/**
* Default language that is used when no language is set in the browser or when a localized string is not found for the current language.
@@ -201,7 +95,6 @@ export const LANGUAGE_CODES = AVAILABLE_LANGUAGES.map(lang => lang.code);
export interface ILanguageConfig {
code: string;
name: string;
nativeName: string;
flag?: string;
}

View File

@@ -395,35 +395,10 @@ declare function getAvailableAgeRanges(): IAgeRangeOption[];
declare function convertAgeRangeToBirthdateOptions(ageRange: string): IBirthdateOptions | null;
/**
* Represents a language option for identity generation.
* Gets all available language codes for identity generation.
* @returns Array of ISO language codes (e.g. ["da", "de", "en", ...]).
*/
interface ILanguageOption {
/**
* The language code (e.g., "en", "nl", "de")
*/
value: string;
/**
* The display label in the native language (e.g., "English", "Nederlands", "Deutsch")
*/
label: string;
/**
* The flag emoji for the language (e.g., "🇬🇧", "🇳🇱", "🇩🇪")
*/
flag: string;
/**
* Alternative language codes that map to this identity generator language.
* Used for matching UI locale codes to identity generator languages.
* For example, "en-US", "en-GB", "en-CA" all map to "en"
*/
alternativeCodes?: string[];
}
/**
* Gets all available languages for identity generation.
* Display labels are in the native language, with optional flag emoji that clients can choose to display.
* Languages are sorted alphabetically by label for consistent display across all platforms.
* @returns Array of language options sorted alphabetically by label
*/
declare function getAvailableLanguages(): ILanguageOption[];
declare function getAvailableLanguages(): string[];
/**
* Maps a UI language code to an identity generator language code.
* If no explicit match is found, returns null to indicate no preference.
@@ -435,7 +410,7 @@ declare function getAvailableLanguages(): ILanguageOption[];
* mapUiLanguageToIdentityLanguage("en-US") // returns "en"
* mapUiLanguageToIdentityLanguage("nl") // returns "nl"
* mapUiLanguageToIdentityLanguage("de-CH") // returns "de"
* mapUiLanguageToIdentityLanguage("fr") // returns null (no French identity generator)
* mapUiLanguageToIdentityLanguage("ja") // returns null (no Japanese identity generator)
*/
declare function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null | undefined): string | null;
@@ -454,4 +429,4 @@ declare const CreateIdentityGenerator: (language: string) => IdentityGenerator;
*/
declare const CreateUsernameEmailGenerator: () => UsernameEmailGenerator;
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type ILanguageOption, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };

View File

@@ -11763,95 +11763,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -11718,95 +11718,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -13,7 +13,7 @@ var MAX_WORD_COUNT = 10;
// src/defaults/Languages.ts
var DEFAULT_LANGUAGE_CODE = "en";
var LANGUAGES = [
{ code: "en", flag: "\u{1F1EC}\u{1F1E7}", label: "English" },
{ code: "en", flag: "\u{1F1FA}\u{1F1F8}", label: "English" },
{ code: "nl", flag: "\u{1F1F3}\u{1F1F1}", label: "Nederlands" },
{ code: "de", flag: "\u{1F1E9}\u{1F1EA}", label: "Deutsch" },
{ code: "fr", flag: "\u{1F1EB}\u{1F1F7}", label: "Fran\xE7ais" },
@@ -23,12 +23,14 @@ var LANGUAGES = [
{ code: "fi", flag: "\u{1F1EB}\u{1F1EE}", label: "Suomi" },
{ code: "he", flag: "\u{1F1EE}\u{1F1F1}", label: "\u05E2\u05D1\u05E8\u05D9\u05EA" },
{ code: "pl", flag: "\u{1F1F5}\u{1F1F1}", label: "Polski" },
{ code: "pt", flag: "\u{1F1E7}\u{1F1F7}", label: "Portugu\xEAs" },
{ code: "pt", flag: "\u{1F1E7}\u{1F1F7}", label: "Portugu\xEAs Brasileiro" },
{ code: "ro", flag: "\u{1F1F7}\u{1F1F4}", label: "Rom\xE2n\u0103" },
{ code: "ru", flag: "\u{1F1F7}\u{1F1FA}", label: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
{ code: "sv", flag: "\u{1F1F8}\u{1F1EA}", label: "Svenska" },
{ code: "uk", flag: "\u{1F1FA}\u{1F1E6}", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
{ code: "zh", flag: "\u{1F1E8}\u{1F1F3}", label: "\u4E2D\u6587" }
{ code: "zh", flag: "\u{1F1E8}\u{1F1F3}", label: "\u7B80\u4F53\u4E2D\u6587" },
{ code: "ur", flag: "\u{1F1F5}\u{1F1F0}", label: "\u0627\u0631\u062F\u0648" },
{ code: "fa", flag: "\u{1F1EE}\u{1F1F7}", label: "\u0641\u0627\u0631\u0633\u06CC" }
];
function normalizeLanguageCode(code) {
return (code ?? "").slice(0, 2).toLowerCase();

View File

@@ -4,7 +4,8 @@ import { useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { getAvailableLanguages, ILanguageOption } from '@/utils/dist/core/identity-generator';
import { getAvailableLanguages } from '@/utils/dist/core/identity-generator';
import { getLanguageInfo } from '@/utils/dist/core/models/defaults';
import { useColors } from '@/hooks/useColorScheme';
import { useVaultMutate } from '@/hooks/useVaultMutate';
@@ -28,7 +29,9 @@ export default function IdentityGeneratorLanguageScreen(): React.ReactNode {
const { executeVaultMutation } = useVaultMutate();
const [language, setLanguage] = useState<string>('en');
const [languageOptions] = useState<ILanguageOption[]>(() => getAvailableLanguages());
const [languageOptions] = useState<string[]>(() =>
getAvailableLanguages().sort((a, b) => getLanguageInfo(a).label.localeCompare(getLanguageInfo(b).label))
);
// Store pending changes and initial values
const pendingChanges = useRef<{ language?: string }>({});
@@ -130,14 +133,15 @@ export default function IdentityGeneratorLanguageScreen(): React.ReactNode {
<View style={styles.optionContainer}>
{languageOptions.map((option, index) => {
const isLast = index === languageOptions.length - 1;
const info = getLanguageInfo(option);
return (
<TouchableOpacity
key={option.value}
key={option}
style={[styles.option, isLast && styles.optionLast]}
onPress={() => handleLanguageSelect(option.value)}
onPress={() => handleLanguageSelect(option)}
>
<ThemedText style={styles.optionText}>{option.flag} {option.label}</ThemedText>
{language === option.value && (
<ThemedText style={styles.optionText}>{info.flag} {info.label}</ThemedText>
{language === option && (
<Ionicons name="checkmark" size={20} style={styles.selectedIcon} />
)}
</TouchableOpacity>

View File

@@ -4,7 +4,8 @@ import { useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { getAvailableAgeRanges, IAgeRangeOption, getAvailableLanguages, ILanguageOption } from '@/utils/dist/core/identity-generator';
import { getAvailableAgeRanges, IAgeRangeOption } from '@/utils/dist/core/identity-generator';
import { getLanguageInfo } from '@/utils/dist/core/models/defaults';
import { useColors } from '@/hooks/useColorScheme';
import { useVaultMutate } from '@/hooks/useVaultMutate';
@@ -29,7 +30,6 @@ export default function IdentityGeneratorSettingsScreen(): React.ReactNode {
const [language, setLanguage] = useState<string>('en');
const [gender, setGender] = useState<string>('random');
const [ageRange, setAgeRange] = useState<string>('random');
const [languageOptions] = useState<ILanguageOption[]>(() => getAvailableLanguages());
const [ageRangeOptions] = useState<IAgeRangeOption[]>(() => getAvailableAgeRanges());
// Store pending changes and initial values (language is managed in subview)
@@ -119,9 +119,9 @@ export default function IdentityGeneratorSettingsScreen(): React.ReactNode {
* Get the display label for the current language
*/
const getLanguageDisplayLabel = useCallback((): string => {
const option = languageOptions.find(opt => opt.value === language);
return option ? `${option.flag} ${option.label}` : language;
}, [language, languageOptions]);
const info = getLanguageInfo(language);
return `${info.flag} ${info.label}`;
}, [language]);
/**
* Handle gender change

View File

@@ -395,35 +395,10 @@ declare function getAvailableAgeRanges(): IAgeRangeOption[];
declare function convertAgeRangeToBirthdateOptions(ageRange: string): IBirthdateOptions | null;
/**
* Represents a language option for identity generation.
* Gets all available language codes for identity generation.
* @returns Array of ISO language codes (e.g. ["da", "de", "en", ...]).
*/
interface ILanguageOption {
/**
* The language code (e.g., "en", "nl", "de")
*/
value: string;
/**
* The display label in the native language (e.g., "English", "Nederlands", "Deutsch")
*/
label: string;
/**
* The flag emoji for the language (e.g., "🇬🇧", "🇳🇱", "🇩🇪")
*/
flag: string;
/**
* Alternative language codes that map to this identity generator language.
* Used for matching UI locale codes to identity generator languages.
* For example, "en-US", "en-GB", "en-CA" all map to "en"
*/
alternativeCodes?: string[];
}
/**
* Gets all available languages for identity generation.
* Display labels are in the native language, with optional flag emoji that clients can choose to display.
* Languages are sorted alphabetically by label for consistent display across all platforms.
* @returns Array of language options sorted alphabetically by label
*/
declare function getAvailableLanguages(): ILanguageOption[];
declare function getAvailableLanguages(): string[];
/**
* Maps a UI language code to an identity generator language code.
* If no explicit match is found, returns null to indicate no preference.
@@ -435,7 +410,7 @@ declare function getAvailableLanguages(): ILanguageOption[];
* mapUiLanguageToIdentityLanguage("en-US") // returns "en"
* mapUiLanguageToIdentityLanguage("nl") // returns "nl"
* mapUiLanguageToIdentityLanguage("de-CH") // returns "de"
* mapUiLanguageToIdentityLanguage("fr") // returns null (no French identity generator)
* mapUiLanguageToIdentityLanguage("ja") // returns null (no Japanese identity generator)
*/
declare function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null | undefined): string | null;
@@ -454,4 +429,4 @@ declare const CreateIdentityGenerator: (language: string) => IdentityGenerator;
*/
declare const CreateUsernameEmailGenerator: () => UsernameEmailGenerator;
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type ILanguageOption, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };

View File

@@ -11763,95 +11763,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -11718,95 +11718,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -13,7 +13,7 @@ var MAX_WORD_COUNT = 10;
// src/defaults/Languages.ts
var DEFAULT_LANGUAGE_CODE = "en";
var LANGUAGES = [
{ code: "en", flag: "\u{1F1EC}\u{1F1E7}", label: "English" },
{ code: "en", flag: "\u{1F1FA}\u{1F1F8}", label: "English" },
{ code: "nl", flag: "\u{1F1F3}\u{1F1F1}", label: "Nederlands" },
{ code: "de", flag: "\u{1F1E9}\u{1F1EA}", label: "Deutsch" },
{ code: "fr", flag: "\u{1F1EB}\u{1F1F7}", label: "Fran\xE7ais" },
@@ -23,12 +23,14 @@ var LANGUAGES = [
{ code: "fi", flag: "\u{1F1EB}\u{1F1EE}", label: "Suomi" },
{ code: "he", flag: "\u{1F1EE}\u{1F1F1}", label: "\u05E2\u05D1\u05E8\u05D9\u05EA" },
{ code: "pl", flag: "\u{1F1F5}\u{1F1F1}", label: "Polski" },
{ code: "pt", flag: "\u{1F1E7}\u{1F1F7}", label: "Portugu\xEAs" },
{ code: "pt", flag: "\u{1F1E7}\u{1F1F7}", label: "Portugu\xEAs Brasileiro" },
{ code: "ro", flag: "\u{1F1F7}\u{1F1F4}", label: "Rom\xE2n\u0103" },
{ code: "ru", flag: "\u{1F1F7}\u{1F1FA}", label: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
{ code: "sv", flag: "\u{1F1F8}\u{1F1EA}", label: "Svenska" },
{ code: "uk", flag: "\u{1F1FA}\u{1F1E6}", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
{ code: "zh", flag: "\u{1F1E8}\u{1F1F3}", label: "\u4E2D\u6587" }
{ code: "zh", flag: "\u{1F1E8}\u{1F1F3}", label: "\u7B80\u4F53\u4E2D\u6587" },
{ code: "ur", flag: "\u{1F1F5}\u{1F1F0}", label: "\u0627\u0631\u062F\u0648" },
{ code: "fa", flag: "\u{1F1EE}\u{1F1F7}", label: "\u0641\u0627\u0631\u0633\u06CC" }
];
function normalizeLanguageCode(code) {
return (code ?? "").slice(0, 2).toLowerCase();

View File

@@ -23,7 +23,7 @@ public static class Languages
/// </summary>
private static readonly (string Code, string Flag, string Label)[] Meta =
[
("en", "🇬🇧", "English"),
("en", "🇺🇸", "English"),
("nl", "🇳🇱", "Nederlands"),
("de", "🇩🇪", "Deutsch"),
("fr", "🇫🇷", "Français"),
@@ -33,12 +33,14 @@ public static class Languages
("fi", "🇫🇮", "Suomi"),
("he", "🇮🇱", "עברית"),
("pl", "🇵🇱", "Polski"),
("pt", "🇧🇷", "Português"),
("pt", "🇧🇷", "Português Brasileiro"),
("ro", "🇷🇴", "Română"),
("ru", "🇷🇺", "Русский"),
("sv", "🇸🇪", "Svenska"),
("uk", "🇺🇦", "Українська"),
("zh", "🇨🇳", "中文"),
("zh", "🇨🇳", "简体中文"),
("ur", "🇵🇰", "اردو"),
("fa", "🇮🇷", "فارسی"),
];
/// <summary>
@@ -72,6 +74,45 @@ public static class Languages
return $"🌐 {code}";
}
/// <summary>
/// Get the flag emoji for an ISO language code. Falls back to a globe flag for unknown languages.
/// </summary>
/// <param name="code">The ISO language code (case-insensitive).</param>
/// <returns>The flag emoji, e.g. "🇳🇱".</returns>
public static string GetFlag(string code)
{
var iso = Normalize(code);
foreach (var (metaCode, flag, _) in Meta)
{
if (metaCode == iso)
{
return flag;
}
}
return "🌐";
}
/// <summary>
/// Get the native display label for an ISO language code. Falls back to the raw code for unknown
/// languages.
/// </summary>
/// <param name="code">The ISO language code (case-insensitive).</param>
/// <returns>The native label, e.g. "Nederlands".</returns>
public static string GetLabel(string code)
{
var iso = Normalize(code);
foreach (var (metaCode, _, label) in Meta)
{
if (metaCode == iso)
{
return label;
}
}
return code;
}
/// <summary>
/// Resolve a default language code for an app/UI language, restricted to a set of available
/// codes. Returns the app language when available, otherwise the first available code,

View File

@@ -9,6 +9,7 @@ namespace AliasVault.Client.Services.JsInterop;
using System.Security.Cryptography;
using System.Text.Json;
using AliasVault.Client.Main.Models;
using AliasVault.Client.Services.JsInterop.Models;
using AliasVault.Shared.Core;
using Microsoft.AspNetCore.Components;
@@ -436,8 +437,21 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
await InitializeAsync();
}
var result = await _identityGeneratorModule!.InvokeAsync<List<LanguageOption>>("getAvailableLanguages");
return result ?? new List<LanguageOption>();
var codes = await _identityGeneratorModule!.InvokeAsync<List<string>>("getAvailableLanguages");
if (codes == null)
{
return new List<LanguageOption>();
}
return codes
.Select(code => new LanguageOption
{
Value = code,
Flag = Languages.GetFlag(code),
Label = Languages.GetLabel(code),
})
.OrderBy(option => option.Label, StringComparer.CurrentCulture)
.ToList();
}
catch (JSException ex)
{

View File

@@ -8,6 +8,7 @@
namespace AliasVault.Client.Services;
using System.Globalization;
using AliasVault.Client.Main.Models;
using AliasVault.Client.Services.Database;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
@@ -23,31 +24,13 @@ public class LanguageService(
DbService dbService)
{
/// <summary>
/// Language configuration containing all supported languages.
/// To add a new language, simply add a new entry to this list.
/// The set of UI languages the client has translations for. Native display names and flag emojis
/// are sourced from the shared <see cref="Languages"/> reference, so adding a new language only
/// requires adding its ISO code here (plus the matching translation resources).
/// </summary>
private static readonly List<LanguageConfig> SupportedLanguages = new()
private static readonly List<string> SupportedLanguageCodes = new()
{
new LanguageConfig("da", "Dansk", "🇩🇰"),
new LanguageConfig("de", "Deutsch", "🇩🇪"),
new LanguageConfig("en", "English", "🇺🇸"),
new LanguageConfig("es", "Español", "🇪🇸"),
new LanguageConfig("fi", "Suomi", "🇫🇮"),
new LanguageConfig("fr", "Français", "🇫🇷"),
new LanguageConfig("he", "עברית", "🇮🇱"),
new LanguageConfig("it", "Italiano", "🇮🇹"),
new LanguageConfig("nl", "Nederlands", "🇳🇱"),
new LanguageConfig("pl", "Polski", "🇵🇱"),
new LanguageConfig("pt", "Português Brasileiro", "🇧🇷"),
new LanguageConfig("ro", "Română", "🇷🇴"),
new LanguageConfig("ru", "Русский", "🇷🇺"),
new LanguageConfig("sv", "Svenska", "🇸🇪"),
new LanguageConfig("uk", "Українська", "🇺🇦"),
new LanguageConfig("zh", "简体中文", "🇨🇳"),
// Add new languages here:
// new LanguageConfig("fr", "Français", "🇫🇷"),
// new LanguageConfig("es", "Español", "🇪🇸"),
"da", "de", "en", "es", "fi", "fr", "he", "it", "nl", "pl", "pt", "ro", "ru", "sv", "uk", "zh",
};
private readonly ILocalStorageService _localStorage = localStorage;
@@ -66,16 +49,7 @@ public class LanguageService(
/// <returns>Dictionary of language codes and display names.</returns>
public static Dictionary<string, string> GetSupportedLanguages()
{
return SupportedLanguages.ToDictionary(lang => lang.Code, lang => lang.DisplayName);
}
/// <summary>
/// Gets the list of supported languages with flag emojis.
/// </summary>
/// <returns>Dictionary of language codes and display names with flag emojis.</returns>
public static Dictionary<string, string> GetSupportedLanguagesWithFlags()
{
return SupportedLanguages.ToDictionary(lang => lang.Code, lang => $"{lang.FlagEmoji} {lang.DisplayName}");
return SupportedLanguageCodes.ToDictionary(code => code, Languages.GetLabel);
}
/// <summary>
@@ -85,8 +59,7 @@ public class LanguageService(
/// <returns>Flag emoji string.</returns>
public static string GetLanguageFlag(string languageCode)
{
var language = SupportedLanguages.FirstOrDefault(lang => lang.Code == languageCode);
return language?.FlagEmoji ?? "🌐";
return Languages.GetFlag(languageCode);
}
/// <summary>
@@ -96,8 +69,7 @@ public class LanguageService(
/// <returns>Display name string.</returns>
public static string GetLanguageDisplayName(string languageCode)
{
var language = SupportedLanguages.FirstOrDefault(lang => lang.Code == languageCode);
return language?.DisplayName ?? languageCode;
return Languages.GetLabel(languageCode);
}
/// <summary>
@@ -107,7 +79,7 @@ public class LanguageService(
/// <returns>True if the language is supported, false otherwise.</returns>
public static bool IsLanguageSupported(string languageCode)
{
return SupportedLanguages.Any(lang => lang.Code == languageCode);
return SupportedLanguageCodes.Contains(languageCode);
}
/// <summary>
@@ -116,7 +88,7 @@ public class LanguageService(
/// <returns>Default language code.</returns>
public static string GetDefaultLanguage()
{
return SupportedLanguages.FirstOrDefault()?.Code ?? "en";
return SupportedLanguageCodes.FirstOrDefault() ?? "en";
}
/// <summary>
@@ -333,9 +305,4 @@ public class LanguageService(
// Ignore migration errors - user can still change language manually
}
}
/// <summary>
/// Configuration for a supported language.
/// </summary>
private sealed record LanguageConfig(string Code, string DisplayName, string FlagEmoji);
}

View File

@@ -395,35 +395,10 @@ declare function getAvailableAgeRanges(): IAgeRangeOption[];
declare function convertAgeRangeToBirthdateOptions(ageRange: string): IBirthdateOptions | null;
/**
* Represents a language option for identity generation.
* Gets all available language codes for identity generation.
* @returns Array of ISO language codes (e.g. ["da", "de", "en", ...]).
*/
interface ILanguageOption {
/**
* The language code (e.g., "en", "nl", "de")
*/
value: string;
/**
* The display label in the native language (e.g., "English", "Nederlands", "Deutsch")
*/
label: string;
/**
* The flag emoji for the language (e.g., "🇬🇧", "🇳🇱", "🇩🇪")
*/
flag: string;
/**
* Alternative language codes that map to this identity generator language.
* Used for matching UI locale codes to identity generator languages.
* For example, "en-US", "en-GB", "en-CA" all map to "en"
*/
alternativeCodes?: string[];
}
/**
* Gets all available languages for identity generation.
* Display labels are in the native language, with optional flag emoji that clients can choose to display.
* Languages are sorted alphabetically by label for consistent display across all platforms.
* @returns Array of language options sorted alphabetically by label
*/
declare function getAvailableLanguages(): ILanguageOption[];
declare function getAvailableLanguages(): string[];
/**
* Maps a UI language code to an identity generator language code.
* If no explicit match is found, returns null to indicate no preference.
@@ -435,7 +410,7 @@ declare function getAvailableLanguages(): ILanguageOption[];
* mapUiLanguageToIdentityLanguage("en-US") // returns "en"
* mapUiLanguageToIdentityLanguage("nl") // returns "nl"
* mapUiLanguageToIdentityLanguage("de-CH") // returns "de"
* mapUiLanguageToIdentityLanguage("fr") // returns null (no French identity generator)
* mapUiLanguageToIdentityLanguage("ja") // returns null (no Japanese identity generator)
*/
declare function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null | undefined): string | null;
@@ -454,4 +429,4 @@ declare const CreateIdentityGenerator: (language: string) => IdentityGenerator;
*/
declare const CreateUsernameEmailGenerator: () => UsernameEmailGenerator;
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type ILanguageOption, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };
export { CreateIdentityGenerator, CreateUsernameEmailGenerator, Gender, type IAgeRangeOption, type IBirthdateOptions, type IDecadeFirstnames, type IIdentityGenerator, type Identity, IdentityGenerator, IdentityGeneratorDa, IdentityGeneratorDe, IdentityGeneratorEn, IdentityGeneratorEs, IdentityGeneratorFa, IdentityGeneratorFr, IdentityGeneratorIt, IdentityGeneratorNl, IdentityGeneratorRo, IdentityGeneratorSv, IdentityGeneratorUr, IdentityHelperUtils, UsernameEmailGenerator, convertAgeRangeToBirthdateOptions, getAvailableAgeRanges, getAvailableLanguages, mapUiLanguageToIdentityLanguage };

View File

@@ -11763,95 +11763,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -11718,95 +11718,39 @@ function convertAgeRangeToBirthdateOptions(ageRange) {
}
// src/utils/LanguageProvider.ts
var LANGUAGE_DEFS = [
{ value: "da", alternativeCodes: ["da-DK"] },
{ value: "de", alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"] },
{ value: "en", alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"] },
{ value: "es", alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"] },
{ value: "fr", alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"] },
{ value: "it", alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"] },
{ value: "nl", alternativeCodes: ["nl-NL", "nl-BE"] },
{ value: "ro", alternativeCodes: ["ro-RO", "ro-MD"] },
{ value: "sv", alternativeCodes: ["sv-SE", "sv-FI"] },
{ value: "ur", alternativeCodes: ["ur-PK", "ur-IN"] },
{ value: "fa", alternativeCodes: ["fa-IR", "fa-AF"] }
];
function getAvailableLanguages() {
const languages = [
{
value: "da",
label: "Dansk",
flag: "\u{1F1E9}\u{1F1F0}",
alternativeCodes: ["da-DK"]
},
{
value: "de",
label: "Deutsch",
flag: "\u{1F1E9}\u{1F1EA}",
alternativeCodes: ["de-DE", "de-AT", "de-CH", "de-LU", "de-LI"]
},
{
value: "en",
label: "English",
flag: "\u{1F1EC}\u{1F1E7}",
alternativeCodes: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ", "en-IE", "en-ZA", "en-SG", "en-IN"]
},
{
value: "es",
label: "Espa\xF1ol",
flag: "\u{1F1EA}\u{1F1F8}",
alternativeCodes: ["es-ES", "es-MX", "es-AR", "es-CO", "es-CL", "es-PE", "es-VE", "es-EC", "es-GT", "es-CU", "es-BO", "es-DO", "es-HN", "es-PY", "es-SV", "es-NI", "es-CR", "es-PA", "es-UY", "es-PR"]
},
{
value: "fr",
label: "Fran\xE7ais",
flag: "\u{1F1EB}\u{1F1F7}",
alternativeCodes: ["fr-FR", "fr-CA", "fr-BE", "fr-CH", "fr-LU", "fr-MC"]
},
{
value: "it",
label: "Italiano",
flag: "\u{1F1EE}\u{1F1F9}",
alternativeCodes: ["it-IT", "it-CH", "it-SM", "it-VA"]
},
{
value: "nl",
label: "Nederlands",
flag: "\u{1F1F3}\u{1F1F1}",
alternativeCodes: ["nl-NL", "nl-BE"]
},
{
value: "ro",
label: "Rom\xE2n\u0103",
flag: "\u{1F1F7}\u{1F1F4}",
alternativeCodes: ["ro-RO", "ro-MD"]
},
{
value: "sv",
label: "Svenska",
flag: "\u{1F1F8}\u{1F1EA}",
alternativeCodes: ["sv-SE", "sv-FI"]
},
{
value: "ur",
label: "\u0627\u0631\u062F\u0648",
flag: "\u{1F1F5}\u{1F1F0}",
alternativeCodes: ["ur-PK", "ur-IN"]
},
{
value: "fa",
label: "\u0641\u0627\u0631\u0633\u06CC",
flag: "\u{1F1EE}\u{1F1F7}",
alternativeCodes: ["fa-IR", "fa-AF"]
}
];
return languages.sort((a, b) => a.label.localeCompare(b.label));
return LANGUAGE_DEFS.map((lang) => lang.value);
}
function mapUiLanguageToIdentityLanguage(uiLanguageCode) {
if (!uiLanguageCode) {
return null;
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
const exactMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
const alternativeMatch = availableLanguages.find(
const alternativeMatch = LANGUAGE_DEFS.find(
(lang) => lang.alternativeCodes?.some((code) => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
return alternativeMatch.value;
}
const baseCode = normalizedCode.split("-")[0];
const baseMatch = availableLanguages.find((lang) => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find((lang) => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}

View File

@@ -87,6 +87,45 @@ ${tuples}
return $"🌐 {code}";
}
/// <summary>
/// Get the flag emoji for an ISO language code. Falls back to a globe flag for unknown languages.
/// </summary>
/// <param name="code">The ISO language code (case-insensitive).</param>
/// <returns>The flag emoji, e.g. "🇳🇱".</returns>
public static string GetFlag(string code)
{
var iso = Normalize(code);
foreach (var (metaCode, flag, _) in Meta)
{
if (metaCode == iso)
{
return flag;
}
}
return "🌐";
}
/// <summary>
/// Get the native display label for an ISO language code. Falls back to the raw code for unknown
/// languages.
/// </summary>
/// <param name="code">The ISO language code (case-insensitive).</param>
/// <returns>The native label, e.g. "Nederlands".</returns>
public static string GetLabel(string code)
{
var iso = Normalize(code);
foreach (var (metaCode, _, label) in Meta)
{
if (metaCode == iso)
{
return label;
}
}
return code;
}
/// <summary>
/// Resolve a default language code for an app/UI language, restricted to a set of available
/// codes. Returns the app language when available, otherwise the first available code,

View File

@@ -23,7 +23,7 @@ export const DEFAULT_LANGUAGE_CODE = 'en';
* Covers the AliasVault app UI languages so this list can be reused beyond a single feature.
*/
export const LANGUAGES: ILanguageInfo[] = [
{ code: 'en', flag: '🇬🇧', label: 'English' },
{ code: 'en', flag: '🇺🇸', label: 'English' },
{ code: 'nl', flag: '🇳🇱', label: 'Nederlands' },
{ code: 'de', flag: '🇩🇪', label: 'Deutsch' },
{ code: 'fr', flag: '🇫🇷', label: 'Français' },
@@ -33,12 +33,14 @@ export const LANGUAGES: ILanguageInfo[] = [
{ code: 'fi', flag: '🇫🇮', label: 'Suomi' },
{ code: 'he', flag: '🇮🇱', label: 'עברית' },
{ code: 'pl', flag: '🇵🇱', label: 'Polski' },
{ code: 'pt', flag: '🇧🇷', label: 'Português' },
{ code: 'pt', flag: '🇧🇷', label: 'Português Brasileiro' },
{ code: 'ro', flag: '🇷🇴', label: 'Română' },
{ code: 'ru', flag: '🇷🇺', label: 'Русский' },
{ code: 'sv', flag: '🇸🇪', label: 'Svenska' },
{ code: 'uk', flag: '🇺🇦', label: 'Українська' },
{ code: 'zh', flag: '🇨🇳', label: '中文' },
{ code: 'zh', flag: '🇨🇳', label: '简体中文' },
{ code: 'ur', flag: '🇵🇰', label: 'اردو' },
{ code: 'fa', flag: '🇮🇷', label: 'فارسی' },
];
/**

View File

@@ -141,6 +141,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -157,6 +158,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -173,6 +175,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -189,6 +192,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -205,6 +209,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -221,6 +226,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -237,6 +243,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -253,6 +260,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -269,6 +277,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -285,6 +294,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -301,6 +311,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -317,6 +328,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -333,6 +345,7 @@
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -349,6 +362,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -365,6 +379,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -381,6 +396,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -397,6 +413,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -413,6 +430,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -429,6 +447,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -445,6 +464,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -461,6 +481,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -477,6 +498,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -493,6 +515,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -509,6 +532,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -525,6 +549,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2826,7 +2851,7 @@
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
"devOptional": true,
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {

View File

@@ -3,14 +3,15 @@ import { getAvailableLanguages, mapUiLanguageToIdentityLanguage } from '../utils
describe('LanguageProvider', () => {
describe('getAvailableLanguages', () => {
it('should return a list of available languages', () => {
it('should return a list of available language codes', () => {
const languages = getAvailableLanguages();
expect(languages).toBeDefined();
expect(languages.length).toBeGreaterThan(0);
expect(languages[0]).toHaveProperty('value');
expect(languages[0]).toHaveProperty('label');
expect(languages[0]).toHaveProperty('flag');
// Returns ISO codes only; display metadata (flag/label) is resolved by clients from core/models.
expect(languages.every(code => typeof code === 'string')).toBe(true);
expect(languages).toContain('en');
expect(languages).toContain('nl');
});
});

View File

@@ -1,108 +1,47 @@
/**
* Represents a language option for identity generation.
* Internal definition of an identity-generator language: its ISO code plus the alternative locale
* codes that map onto it. Display metadata (flag, native label) is intentionally NOT stored here —
* clients look that up from the shared `core/models` language reference so it lives in one place.
*/
export interface ILanguageOption {
interface ILanguageDef {
/**
* The language code (e.g., "en", "nl", "de")
* The language code (e.g., "en", "nl", "de").
*/
value: string;
/**
* The display label in the native language (e.g., "English", "Nederlands", "Deutsch")
*/
label: string;
/**
* The flag emoji for the language (e.g., "🇬🇧", "🇳🇱", "🇩🇪")
*/
flag: string;
/**
* Alternative language codes that map to this identity generator language.
* Used for matching UI locale codes to identity generator languages.
* For example, "en-US", "en-GB", "en-CA" all map to "en"
* For example, "en-US", "en-GB", "en-CA" all map to "en".
*/
alternativeCodes?: string[];
}
/**
* Gets all available languages for identity generation.
* Display labels are in the native language, with optional flag emoji that clients can choose to display.
* Languages are sorted alphabetically by label for consistent display across all platforms.
* @returns Array of language options sorted alphabetically by label
* The languages supported by the identity generator. Only ISO codes and their alternative locale
* codes are defined here; flag emojis and native labels come from the shared `core/models` language
* reference (`getLanguageInfo`).
*/
export function getAvailableLanguages(): ILanguageOption[] {
const languages: ILanguageOption[] = [
{
value: 'da',
label: 'Dansk',
flag: '🇩🇰',
alternativeCodes: ['da-DK']
},
{
value: 'de',
label: 'Deutsch',
flag: '🇩🇪',
alternativeCodes: ['de-DE', 'de-AT', 'de-CH', 'de-LU', 'de-LI']
},
{
value: 'en',
label: 'English',
flag: '🇬🇧',
alternativeCodes: ['en-US', 'en-GB', 'en-CA', 'en-AU', 'en-NZ', 'en-IE', 'en-ZA', 'en-SG', 'en-IN']
},
{
value: 'es',
label: 'Español',
flag: '🇪🇸',
alternativeCodes: ['es-ES', 'es-MX', 'es-AR', 'es-CO', 'es-CL', 'es-PE', 'es-VE', 'es-EC', 'es-GT', 'es-CU', 'es-BO', 'es-DO', 'es-HN', 'es-PY', 'es-SV', 'es-NI', 'es-CR', 'es-PA', 'es-UY', 'es-PR']
},
{
value: 'fr',
label: 'Français',
flag: '🇫🇷',
alternativeCodes: ['fr-FR', 'fr-CA', 'fr-BE', 'fr-CH', 'fr-LU', 'fr-MC']
},
{
value: 'it',
label: 'Italiano',
flag: '🇮🇹',
alternativeCodes: ['it-IT', 'it-CH', 'it-SM', 'it-VA']
},
{
value: 'nl',
label: 'Nederlands',
flag: '🇳🇱',
alternativeCodes: ['nl-NL', 'nl-BE']
},
{
value: 'ro',
label: 'Română',
flag: '🇷🇴',
alternativeCodes: ['ro-RO', 'ro-MD']
},
{
value: 'sv',
label: 'Svenska',
flag: '🇸🇪',
alternativeCodes: ['sv-SE', 'sv-FI']
},
{
value: 'ur',
label: 'اردو',
flag: '🇵🇰',
alternativeCodes: ['ur-PK', 'ur-IN']
},
{
value: 'fa',
label: 'فارسی',
flag: '🇮🇷',
alternativeCodes: ['fa-IR', 'fa-AF']
}
];
const LANGUAGE_DEFS: ILanguageDef[] = [
{ value: 'da', alternativeCodes: ['da-DK'] },
{ value: 'de', alternativeCodes: ['de-DE', 'de-AT', 'de-CH', 'de-LU', 'de-LI'] },
{ value: 'en', alternativeCodes: ['en-US', 'en-GB', 'en-CA', 'en-AU', 'en-NZ', 'en-IE', 'en-ZA', 'en-SG', 'en-IN'] },
{ value: 'es', alternativeCodes: ['es-ES', 'es-MX', 'es-AR', 'es-CO', 'es-CL', 'es-PE', 'es-VE', 'es-EC', 'es-GT', 'es-CU', 'es-BO', 'es-DO', 'es-HN', 'es-PY', 'es-SV', 'es-NI', 'es-CR', 'es-PA', 'es-UY', 'es-PR'] },
{ value: 'fr', alternativeCodes: ['fr-FR', 'fr-CA', 'fr-BE', 'fr-CH', 'fr-LU', 'fr-MC'] },
{ value: 'it', alternativeCodes: ['it-IT', 'it-CH', 'it-SM', 'it-VA'] },
{ value: 'nl', alternativeCodes: ['nl-NL', 'nl-BE'] },
{ value: 'ro', alternativeCodes: ['ro-RO', 'ro-MD'] },
{ value: 'sv', alternativeCodes: ['sv-SE', 'sv-FI'] },
{ value: 'ur', alternativeCodes: ['ur-PK', 'ur-IN'] },
{ value: 'fa', alternativeCodes: ['fa-IR', 'fa-AF'] },
];
// Sort alphabetically by label using locale-aware comparison
return languages.sort((a, b) => a.label.localeCompare(b.label));
/**
* Gets all available language codes for identity generation.
* @returns Array of ISO language codes (e.g. ["da", "de", "en", ...]).
*/
export function getAvailableLanguages(): string[] {
return LANGUAGE_DEFS.map(lang => lang.value);
}
/**
@@ -116,7 +55,7 @@ export function getAvailableLanguages(): ILanguageOption[] {
* mapUiLanguageToIdentityLanguage("en-US") // returns "en"
* mapUiLanguageToIdentityLanguage("nl") // returns "nl"
* mapUiLanguageToIdentityLanguage("de-CH") // returns "de"
* mapUiLanguageToIdentityLanguage("fr") // returns null (no French identity generator)
* mapUiLanguageToIdentityLanguage("ja") // returns null (no Japanese identity generator)
*/
export function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null | undefined): string | null {
if (!uiLanguageCode) {
@@ -124,16 +63,15 @@ export function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null |
}
const normalizedCode = uiLanguageCode.toLowerCase();
const availableLanguages = getAvailableLanguages();
// First, try exact match with the primary value
const exactMatch = availableLanguages.find(lang => lang.value.toLowerCase() === normalizedCode);
const exactMatch = LANGUAGE_DEFS.find(lang => lang.value.toLowerCase() === normalizedCode);
if (exactMatch) {
return exactMatch.value;
}
// Then, try matching with alternative codes
const alternativeMatch = availableLanguages.find(lang =>
const alternativeMatch = LANGUAGE_DEFS.find(lang =>
lang.alternativeCodes?.some(code => code.toLowerCase() === normalizedCode)
);
if (alternativeMatch) {
@@ -142,7 +80,7 @@ export function mapUiLanguageToIdentityLanguage(uiLanguageCode: string | null |
// Finally, try matching the base language code (e.g., "en" from "en-US")
const baseCode = normalizedCode.split('-')[0];
const baseMatch = availableLanguages.find(lang => lang.value.toLowerCase() === baseCode);
const baseMatch = LANGUAGE_DEFS.find(lang => lang.value.toLowerCase() === baseCode);
if (baseMatch) {
return baseMatch.value;
}