You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
9.0 KiB

/**
* ============================================================================
* INTERNATIONALIZATION (i18n) - Utilities
* ============================================================================
*
* Sistema de internacionalización para textos multiidioma
*
* ============================================================================
*/
import type {SupportedLocale, I18nString, I18nTranslations, LocaleInfo} from "../types/i18n.ts";
import { DEFAULT_LOCALE } from "../types/i18n.ts";
// ============================================================================
// SUPPORTED LOCALES
// ============================================================================
/**
* Lista de todos los locales soportados
*/
export const SUPPORTED_LOCALES: readonly SupportedLocale[] = [
'es',
'en',
'de',
'fr',
'it',
'pt',
'ca',
'eu',
'gl'
] as const;
// ============================================================================
// LOCALE UTILITIES
// ============================================================================
/**
* Verificar si un string es un locale soportado
*/
export function isSupportedLocale(locale: string): locale is SupportedLocale {
return SUPPORTED_LOCALES.includes(locale as SupportedLocale);
}
/**
* Obtener locale válido con fallback
*/
export function getValidLocale(
locale: string | undefined,
fallback: SupportedLocale = DEFAULT_LOCALE
): SupportedLocale {
if (!locale) return fallback;
return isSupportedLocale(locale) ? locale : fallback;
}
/**
* Extraer código de idioma de un locale (ej: "es-ES" → "es")
*/
export function extractLanguageCode(locale: string): string {
return locale.split('-')[0].toLowerCase();
}
// ============================================================================
// TRANSLATION UTILITIES
// ============================================================================
/**
* Obtener texto traducido
*
* @param text - String i18n (simple o con traducciones)
* @param locale - Locale objetivo
* @param fallback - Locale de respaldo si no existe la traducción
* @returns Texto en el idioma solicitado
*
* @example
* const text: I18nString = { es: "Hola", en: "Hello" };
* t(text, 'es') // → "Hola"
* t(text, 'fr') // → "Hola" (fallback a español)
* t("OK", 'es') // → "OK" (string simple)
*/
export function t(
text: I18nString,
locale: SupportedLocale,
fallback: SupportedLocale = DEFAULT_LOCALE
): string {
// Si es string simple, devolverlo directamente
if (typeof text === 'string') {
return text;
}
// Intentar con el locale solicitado
if (text[locale]) {
return text[locale];
}
// Fallback al locale de respaldo
if (text[fallback]) {
return text[fallback];
}
// Último recurso: primer valor disponible
const firstAvailable = Object.values(text)[0];
return firstAvailable || '';
}
/**
* Alias de `t` para compatibilidad con librerías i18n
*/
export const translate = t;
/**
* Verificar si un I18nString tiene traducción para un locale
*/
export function hasTranslation(
text: I18nString,
locale: SupportedLocale
): boolean {
if (typeof text === 'string') return true;
return text[locale] !== undefined;
}
/**
* Obtener todos los locales disponibles en un I18nString
*/
export function getAvailableLocales(text: I18nString): SupportedLocale[] {
if (typeof text === 'string') return [...SUPPORTED_LOCALES];
return Object.keys(text) as SupportedLocale[];
}
/**
* Verificar si un I18nString está completo (tiene todas las traducciones)
*/
export function isComplete(text: I18nString): boolean {
if (typeof text === 'string') return true;
return SUPPORTED_LOCALES.every(locale => text[locale] !== undefined);
}
/**
* Obtener locales faltantes en un I18nString
*/
export function getMissingLocales(text: I18nString): SupportedLocale[] {
if (typeof text === 'string') return [];
return SUPPORTED_LOCALES.filter(locale => text[locale] === undefined);
}
// ============================================================================
// I18N BUILDERS
// ============================================================================
/**
* Crear I18nString desde objeto parcial
* Garantiza que al menos existe el locale por defecto
*/
export function createI18nString(
translations: Partial<Record<SupportedLocale, string>>
): I18nString {
if (!translations[DEFAULT_LOCALE]) {
throw new Error(`I18nString must have at least '${DEFAULT_LOCALE}' translation`);
}
return translations as I18nTranslations;
}
/**
* Crear I18nString simple (mismo texto en todos los idiomas)
*/
export function createSimpleI18nString(text: string): I18nString {
return text;
}
/**
* Convertir string simple a objeto de traducciones
* (útil para migración)
*/
export function expandToTranslations(
text: I18nString,
defaultLocale: SupportedLocale = DEFAULT_LOCALE
): I18nTranslations {
if (typeof text === 'string') {
return { [defaultLocale]: text } as I18nTranslations;
}
return text;
}
/**
* Combinar múltiples I18nStrings
* (útil para concatenar textos)
*/
export function combineI18nStrings(
strings: I18nString[],
separator: string = ' '
): I18nString {
// Si todos son strings simples, concatenar directamente
if (strings.every(s => typeof s === 'string')) {
return strings.join(separator);
}
// Crear objeto con todas las traducciones
const combined: Partial<Record<SupportedLocale, string>> = {};
for (const locale of SUPPORTED_LOCALES) {
const parts = strings.map(s => t(s, locale));
combined[locale] = parts.join(separator);
}
return combined as I18nTranslations;
}
// ============================================================================
// LOCALE METADATA
// ============================================================================
/**
* Metadata de locales soportados
*/
export const LOCALE_INFO: Record<SupportedLocale, LocaleInfo> = {
es: {
code: 'es',
name: 'Spanish',
nativeName: 'Español',
flag: '🇪🇸'
},
en: {
code: 'en',
name: 'English',
nativeName: 'English',
flag: '🇬🇧'
},
de: {
code: 'de',
name: 'German',
nativeName: 'Deutsch',
flag: '🇩🇪'
},
fr: {
code: 'fr',
name: 'French',
nativeName: 'Français',
flag: '🇫🇷'
},
it: {
code: 'it',
name: 'Italian',
nativeName: 'Italiano',
flag: '🇮🇹'
},
pt: {
code: 'pt',
name: 'Portuguese',
nativeName: 'Português',
flag: '🇵🇹'
},
ca: {
code: 'ca',
name: 'Catalan',
nativeName: 'Català',
flag: '🏴'
},
eu: {
code: 'eu',
name: 'Basque',
nativeName: 'Euskara',
flag: '🏴'
},
gl: {
code: 'gl',
name: 'Galician',
nativeName: 'Galego',
flag: '🏴'
}
};
/**
* Obtener información de un locale
*/
export function getLocaleInfo(locale: SupportedLocale): LocaleInfo {
return LOCALE_INFO[locale];
}
// ============================================================================
// VALIDATION
// ============================================================================
/**
* Validar que un I18nString es correcto
*/
export function validateI18nString(text: unknown): text is I18nString {
if (typeof text === 'string') return true;
if (typeof text !== 'object' || text === null) return false;
const obj = text as Record<string, unknown>;
// Debe tener al menos el locale por defecto
if (typeof obj[DEFAULT_LOCALE] !== 'string') return false;
// Todos los valores deben ser strings
return Object.values(obj).every(v => typeof v === 'string');
}
/**
* Error para I18nString inválido
*/
export class I18nStringError extends Error {
constructor(message: string) {
super(`[I18n] ${message}`);
this.name = 'I18nStringError';
}
}
/**
* Validar y lanzar error si es inválido
*/
export function assertValidI18nString(text: unknown): asserts text is I18nString {
if (!validateI18nString(text)) {
throw new I18nStringError(
`Invalid I18nString. Must be a string or object with at least '${DEFAULT_LOCALE}' key.`
);
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default {
// Constantes y Metadata
SUPPORTED_LOCALES,
// Utilities de Locale
isSupportedLocale,
getValidLocale,
extractLanguageCode,
getLocaleInfo,
// Traducción
t,
translate,
hasTranslation,
getAvailableLocales,
isComplete,
getMissingLocales,
// Builders
createI18nString,
createSimpleI18nString,
expandToTranslations,
combineI18nStrings,
// Validación y Errores
validateI18nString,
assertValidI18nString,
I18nStringError
};

Powered by TurnKey Linux.