import { LOCALES } from "./config"; import type { Locale } from "./config"; /** * Get a nested value from an object using a dot-notation path * @example getNestedValue({ a: { b: "c" } }, "a.b") // "c" */ export function getNestedValue(obj: T, path: string): unknown { return path.split(".").reduce((current, key) => { if (current && typeof current === "object" && key in current) { return (current as Record)[key]; } return undefined; }, obj); } /** * Format a translation string with parameters * Supports both {{key}} syntax and simple function-based interpolation */ export function formatTranslation( template: string, params: Record, ): string { if (Object.keys(params).length === 0) return template; return template.replace(/\{{1,2}(\w+)\}{1,2}/g, (_match, key) => { return params[key]?.toString() ?? `{${key}}`; }); } /** * Deep merge two objects */ export function deepMerge>( target: T, source: Partial, ): T { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) { if (result[key] && typeof result[key] === "object" && !Array.isArray(result[key])) { result[key] = deepMerge( result[key] as Record, source[key] as Record, ) as T[Extract]; } else { result[key] = source[key] as T[Extract]; } } else { result[key] = source[key] as T[Extract]; } } return result; } /** * Validate locale string */ export function isValidLocale(locale: string): locale is Locale { return (LOCALES as readonly string[]).includes(locale); }