Files
portfolio/lib/i18n-loader.ts
2026-01-22 20:56:35 +01:00

134 lines
3.3 KiB
TypeScript

/**
* i18n Loader with Directus + JSON Fallback
* - Fetches from Directus first
* - Falls back to JSON files if not found
* - Caches results (5 min TTL)
*/
import { getMessage, getContentPage } from './directus';
import enMessages from '@/messages/en.json';
import deMessages from '@/messages/de.json';
const jsonFallback = { en: enMessages, de: deMessages };
// Simple in-memory cache
const cache = new Map<string, { value: any; expires: number }>();
function setCached(key: string, value: any, ttlSeconds = 300) {
cache.set(key, { value, expires: Date.now() + ttlSeconds * 1000 });
}
function getCached(key: string): any | null {
const hit = cache.get(key);
if (!hit) return null;
if (Date.now() > hit.expires) {
cache.delete(key);
return null;
}
return hit.value;
}
/**
* Get a localized message by key
* Tries: Directus (requested locale) → Directus (EN) → JSON (requested locale) → JSON (EN)
*/
export async function getLocalizedMessage(
key: string,
locale: string
): Promise<string> {
const cacheKey = `msg:${key}:${locale}`;
const cached = getCached(cacheKey);
if (cached !== null) return cached;
// Try Directus with requested locale
const dbValue = await getMessage(key, locale);
if (dbValue) {
setCached(cacheKey, dbValue);
return dbValue;
}
// Fallback to EN in Directus if not EN already
if (locale !== 'en') {
const dbValueEn = await getMessage(key, 'en');
if (dbValueEn) {
setCached(cacheKey, dbValueEn);
return dbValueEn;
}
}
// Fallback to JSON file (normalize locale to 'en' or 'de')
const normalizedLocale = locale.startsWith('de') ? 'de' : 'en';
const jsonValue = getNestedValue(jsonFallback[normalizedLocale as 'en' | 'de'], key);
if (jsonValue) {
setCached(cacheKey, jsonValue);
return jsonValue;
}
// Fallback to EN JSON
if (normalizedLocale !== 'en') {
const jsonValueEn = getNestedValue(jsonFallback['en'], key);
if (jsonValueEn) {
setCached(cacheKey, jsonValueEn);
return jsonValueEn;
}
}
// Fallback: return the key itself
return key;
}
/**
* Get a localized content page by slug
* Tries: Directus (requested locale) → Directus (EN)
*/
export async function getLocalizedContent(
slug: string,
locale: string
): Promise<any | null> {
const cacheKey = `page:${slug}:${locale}`;
const cached = getCached(cacheKey);
if (cached !== null) return cached;
if (cached === null && cache.has(cacheKey)) return null; // Already checked, not found
// Try Directus with requested locale
const dbPage = await getContentPage(slug, locale);
if (dbPage) {
setCached(cacheKey, dbPage);
return dbPage;
}
// Fallback to EN in Directus
if (locale !== 'en') {
const dbPageEn = await getContentPage(slug, 'en');
if (dbPageEn) {
setCached(cacheKey, dbPageEn);
return dbPageEn;
}
}
// Not found
setCached(cacheKey, null);
return null;
}
/**
* Helper: Get nested value from object
* Example: "nav.home" → obj.nav.home
*/
function getNestedValue(obj: any, path: string): any {
const keys = path.split('.');
let value = obj;
for (const key of keys) {
value = value?.[key];
if (value === undefined) return null;
}
return value;
}
/**
* Clear cache (useful for webhooks/revalidation)
*/
export function clearI18nCache() {
cache.clear();
}