fix: load translations from API instead of static CDN files
Some checks failed
CI / API Lint (push) Has been cancelled
CI / Admin UI Tests (push) Has been cancelled
CI / Admin UI Build (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / API Tests (push) Has been cancelled
CI / Scanner Lint (push) Has been cancelled
CI / Scanner Tests (push) Has been cancelled
CI / Banner Lint & Typecheck (push) Has been cancelled
CI / Banner Tests (push) Has been cancelled
CI / Banner Build (push) Has been cancelled
CI / Admin UI Typecheck (push) Has been cancelled

The banner was fetching /translations-{locale}.json from the CDN as
static files, but translations are stored in the DB and served via
the public /api/v1/translations/{siteId}/{locale} endpoint.

Fixes:
- fetchTranslations() now calls the public API endpoint
- loadTranslations() takes (apiBase, siteId, locale)
- banner.ts passes apiBase and siteId to loadTranslations()
- i18n.test.ts updated to match new signature
This commit is contained in:
Kunthawat Greethong
2026-06-15 18:30:02 +07:00
parent e9bae32ee2
commit 683aa2379d
5 changed files with 25 additions and 14 deletions

View File

@@ -1,12 +1,13 @@
{ {
"name": "@cmp/banner", "name": "@consentos/banner",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@cmp/banner", "name": "@consentos/banner",
"version": "0.1.0", "version": "0.1.0",
"license": "Elastic-2.0",
"devDependencies": { "devDependencies": {
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.2",

View File

@@ -135,7 +135,7 @@ describe('i18n', () => {
describe('loadTranslations', () => { describe('loadTranslations', () => {
it('should return defaults for English locale', async () => { it('should return defaults for English locale', async () => {
const t = await loadTranslations('https://cdn.example.com', 'en'); const t = await loadTranslations('https://api.example.com', 'site-123', 'en');
expect(t.title).toBe(DEFAULT_TRANSLATIONS.title); expect(t.title).toBe(DEFAULT_TRANSLATIONS.title);
expect(t.acceptAll).toBe(DEFAULT_TRANSLATIONS.acceptAll); expect(t.acceptAll).toBe(DEFAULT_TRANSLATIONS.acceptAll);
}); });
@@ -143,10 +143,10 @@ describe('i18n', () => {
it('should merge remote translations over defaults', async () => { it('should merge remote translations over defaults', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true, ok: true,
json: () => Promise.resolve({ title: 'Wir verwenden Cookies', acceptAll: 'Alle akzeptieren' }), json: () => Promise.resolve({ strings: { title: 'Wir verwenden Cookies', acceptAll: 'Alle akzeptieren' } }),
})); }));
const t = await loadTranslations('https://cdn.example.com', 'de'); const t = await loadTranslations('https://api.example.com', 'site-123', 'de');
expect(t.title).toBe('Wir verwenden Cookies'); expect(t.title).toBe('Wir verwenden Cookies');
expect(t.acceptAll).toBe('Alle akzeptieren'); expect(t.acceptAll).toBe('Alle akzeptieren');
@@ -159,7 +159,7 @@ describe('i18n', () => {
it('should fall back to defaults when fetch fails', async () => { it('should fall back to defaults when fetch fails', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fail'))); vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fail')));
const t = await loadTranslations('https://cdn.example.com', 'fr'); const t = await loadTranslations('https://api.example.com', 'site-123', 'fr');
expect(t.title).toBe(DEFAULT_TRANSLATIONS.title); expect(t.title).toBe(DEFAULT_TRANSLATIONS.title);
vi.unstubAllGlobals(); vi.unstubAllGlobals();

View File

@@ -222,7 +222,7 @@ async function init(): Promise<void> {
// Load translations // Load translations
// Use site-configured default_language if set, otherwise auto-detect // Use site-configured default_language if set, otherwise auto-detect
const locale = config.default_language ?? detectLocale(); const locale = config.default_language ?? detectLocale();
const t = await loadTranslations(cdnBase, locale); const t = await loadTranslations(apiBase, siteId, locale);
// Capture a closure that re-opens the banner with current consent // Capture a closure that re-opens the banner with current consent
// pre-filled. Called from the floating button and from // pre-filled. Called from the floating button and from

View File

@@ -84,34 +84,38 @@ export function normaliseLocale(locale: string): string {
} }
/** /**
* Fetch translations for a locale from the CDN. * Fetch translations for a locale from the public API endpoint.
* Returns null if not found or on error. * Returns null if not found or on error.
*/ */
export async function fetchTranslations( export async function fetchTranslations(
cdnBase: string, apiBase: string,
siteId: string,
locale: string, locale: string,
): Promise<Partial<TranslationStrings> | null> { ): Promise<Partial<TranslationStrings> | null> {
try { try {
const resp = await fetch(`${cdnBase}/translations-${locale}.json`); const resp = await fetch(`${apiBase}/api/v1/translations/${siteId}/${locale}`);
if (!resp.ok) return null; if (!resp.ok) return null;
return (await resp.json()) as Partial<TranslationStrings>; // API returns { strings: { ... } }
const data = (await resp.json()) as { strings?: Partial<TranslationStrings> };
return data.strings ?? null;
} catch { } catch {
return null; return null;
} }
} }
/** /**
* Load translations: try fetching from CDN, fall back to defaults. * Load translations: try fetching from API, fall back to defaults.
*/ */
export async function loadTranslations( export async function loadTranslations(
cdnBase: string, apiBase: string,
siteId: string,
locale: string, locale: string,
): Promise<TranslationStrings> { ): Promise<TranslationStrings> {
if (locale === 'en') { if (locale === 'en') {
return { ...DEFAULT_TRANSLATIONS }; return { ...DEFAULT_TRANSLATIONS };
} }
const remote = await fetchTranslations(cdnBase, locale); const remote = await fetchTranslations(apiBase, siteId, locale);
if (!remote) { if (!remote) {
return { ...DEFAULT_TRANSLATIONS }; return { ...DEFAULT_TRANSLATIONS };
} }

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "consentos",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}