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",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cmp/banner",
"name": "@consentos/banner",
"version": "0.1.0",
"license": "Elastic-2.0",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",

View File

@@ -135,7 +135,7 @@ describe('i18n', () => {
describe('loadTranslations', () => {
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.acceptAll).toBe(DEFAULT_TRANSLATIONS.acceptAll);
});
@@ -143,10 +143,10 @@ describe('i18n', () => {
it('should merge remote translations over defaults', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
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.acceptAll).toBe('Alle akzeptieren');
@@ -159,7 +159,7 @@ describe('i18n', () => {
it('should fall back to defaults when fetch fails', async () => {
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);
vi.unstubAllGlobals();

View File

@@ -222,7 +222,7 @@ async function init(): Promise<void> {
// Load translations
// Use site-configured default_language if set, otherwise auto-detect
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
// 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.
*/
export async function fetchTranslations(
cdnBase: string,
apiBase: string,
siteId: string,
locale: string,
): Promise<Partial<TranslationStrings> | null> {
try {
const resp = await fetch(`${cdnBase}/translations-${locale}.json`);
const resp = await fetch(`${apiBase}/api/v1/translations/${siteId}/${locale}`);
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 {
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(
cdnBase: string,
apiBase: string,
siteId: string,
locale: string,
): Promise<TranslationStrings> {
if (locale === 'en') {
return { ...DEFAULT_TRANSLATIONS };
}
const remote = await fetchTranslations(cdnBase, locale);
const remote = await fetchTranslations(apiBase, siteId, locale);
if (!remote) {
return { ...DEFAULT_TRANSLATIONS };
}

6
package-lock.json generated Normal file
View File

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