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
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:
5
apps/banner/package-lock.json
generated
5
apps/banner/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "consentos",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user