fix: add built-in Thai banner translations
Some checks failed
CI / Detect changes (push) Has been cancelled
CI / API Lint (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
CI / Admin UI Tests (push) Has been cancelled
CI / Admin UI Build (push) Has been cancelled
Some checks failed
CI / Detect changes (push) Has been cancelled
CI / API Lint (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
CI / Admin UI Tests (push) Has been cancelled
CI / Admin UI Build (push) Has been cancelled
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_TRANSLATIONS,
|
DEFAULT_TRANSLATIONS,
|
||||||
|
THAI_TRANSLATIONS,
|
||||||
detectLocale,
|
detectLocale,
|
||||||
fetchTranslations,
|
fetchTranslations,
|
||||||
interpolate,
|
interpolate,
|
||||||
@@ -46,6 +47,43 @@ describe('i18n', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('THAI_TRANSLATIONS', () => {
|
||||||
|
it('should have all required keys', () => {
|
||||||
|
expect(THAI_TRANSLATIONS.title).toBe('เรามีการใช้คุ๊กกี้เพื่อเก็บข้อมูล');
|
||||||
|
expect(THAI_TRANSLATIONS.acceptAll).toBe('ยอมรับทั้งหมด');
|
||||||
|
expect(THAI_TRANSLATIONS.rejectAll).toBe('ปฏิเสธทั้งหมด');
|
||||||
|
expect(THAI_TRANSLATIONS.managePreferences).toBe('ตั้งค่า');
|
||||||
|
expect(THAI_TRANSLATIONS.savePreferences).toBe('บันทึก');
|
||||||
|
expect(THAI_TRANSLATIONS.privacyPolicyLink).toBe('นโยบายความเป็นส่วนตัว');
|
||||||
|
expect(THAI_TRANSLATIONS.closeLabel).toBe('ปิด');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have all category translations', () => {
|
||||||
|
expect(THAI_TRANSLATIONS.categoryNecessary).toBe('จำเป็น');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryFunctional).toBe('ฟังก์ชั่น');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryAnalytics).toBe('การวิเคราะห์');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryMarketing).toBe('การตลาด');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryPersonalisation).toBe('ส่วนตัว');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have category descriptions', () => {
|
||||||
|
expect(THAI_TRANSLATIONS.categoryNecessaryDesc).toContain('การจดจำตะกร้าสินค้า');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryFunctionalDesc).toContain('ฟังก์ชั่นพิเศษ');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryAnalyticsDesc).toContain('ผู้เข้าชมใช้งานเว็บไซต์อย่างไร');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryMarketingDesc).toContain('Facebook และ Google');
|
||||||
|
expect(THAI_TRANSLATIONS.categoryPersonalisationDesc).toContain('เก็บข้อมูลส่วนตัว');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have cookie count template with placeholder', () => {
|
||||||
|
expect(THAI_TRANSLATIONS.cookieCount).toContain('{{count}}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve legal link placeholders in description', () => {
|
||||||
|
expect(THAI_TRANSLATIONS.description).toContain('{{privacy_policy}}');
|
||||||
|
expect(THAI_TRANSLATIONS.description).toContain('{{terms}}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('normaliseLocale', () => {
|
describe('normaliseLocale', () => {
|
||||||
it('should extract language code from locale', () => {
|
it('should extract language code from locale', () => {
|
||||||
expect(normaliseLocale('en-GB')).toBe('en');
|
expect(normaliseLocale('en-GB')).toBe('en');
|
||||||
@@ -140,16 +178,31 @@ describe('i18n', () => {
|
|||||||
expect(t.acceptAll).toBe(DEFAULT_TRANSLATIONS.acceptAll);
|
expect(t.acceptAll).toBe(DEFAULT_TRANSLATIONS.acceptAll);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should merge raw public API translations over defaults', async () => {
|
it('should return built-in Thai without calling API', async () => {
|
||||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
const fetchSpy = vi.fn();
|
||||||
ok: true,
|
vi.stubGlobal('fetch', fetchSpy);
|
||||||
json: () => Promise.resolve({ title: 'เราใช้คุกกี้', acceptAll: 'ยอมรับทั้งหมด' }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const t = await loadTranslations('https://api.example.com', 'site-123', 'th');
|
const t = await loadTranslations('https://api.example.com', 'site-123', 'th');
|
||||||
|
|
||||||
expect(t.title).toBe('เราใช้คุกกี้');
|
expect(t.title).toBe('เรามีการใช้คุ๊กกี้เพื่อเก็บข้อมูล');
|
||||||
expect(t.acceptAll).toBe('ยอมรับทั้งหมด');
|
expect(t.acceptAll).toBe('ยอมรับทั้งหมด');
|
||||||
|
expect(t.rejectAll).toBe('ปฏิเสธทั้งหมด');
|
||||||
|
// Should NOT have called fetch — Thai is built-in
|
||||||
|
expect(fetchSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge raw public API translations over defaults', async () => {
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ title: 'Nous utilisons des cookies', acceptAll: 'Tout accepter' }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const t = await loadTranslations('https://api.example.com', 'site-123', 'fr');
|
||||||
|
|
||||||
|
expect(t.title).toBe('Nous utilisons des cookies');
|
||||||
|
expect(t.acceptAll).toBe('Tout accepter');
|
||||||
// Missing keys should fall back to English
|
// Missing keys should fall back to English
|
||||||
expect(t.rejectAll).toBe(DEFAULT_TRANSLATIONS.rejectAll);
|
expect(t.rejectAll).toBe(DEFAULT_TRANSLATIONS.rejectAll);
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,36 @@ export const DEFAULT_TRANSLATIONS: TranslationStrings = {
|
|||||||
cookieCount: '{{count}} cookies used on this site',
|
cookieCount: '{{count}} cookies used on this site',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Built-in Thai translations — no API call needed. */
|
||||||
|
export const THAI_TRANSLATIONS: TranslationStrings = {
|
||||||
|
title: 'เรามีการใช้คุ๊กกี้เพื่อเก็บข้อมูล',
|
||||||
|
description:
|
||||||
|
'เว็บไซซ์นี้ใช้คุกกี้เพื่อปรับปรุงประสบการณ์การใช้งาน วิเคราะห์การเข้าชม และแสดงโฆษณาที่เหมาะกับคุณ คุณสามารถเลือกได้ว่าจะอนุญาตคุกกี้ประเภทใด คุกกี้ที่จำเป็นจะถูกเปิดใช้งานเสมอเพื่อให้เว็บไซต์ทำงานได้ [นโยบายความเป็นส่วนตัว]({{privacy_policy}}) [ข้อกำหนดและเงื่อนไข]({{terms}})',
|
||||||
|
acceptAll: 'ยอมรับทั้งหมด',
|
||||||
|
rejectAll: 'ปฏิเสธทั้งหมด',
|
||||||
|
managePreferences: 'ตั้งค่า',
|
||||||
|
savePreferences: 'บันทึก',
|
||||||
|
privacyPolicyLink: 'นโยบายความเป็นส่วนตัว',
|
||||||
|
closeLabel: 'ปิด',
|
||||||
|
categoryNecessary: 'จำเป็น',
|
||||||
|
categoryNecessaryDesc: 'คุกกี้ที่จำเป็นสำหรับการทำงานพื้นฐานของเว็บไซต์ เช่น การจดจำตะกร้าสินค้า การเข้าสู่ระบบ และความปลอดภัย — เปิดใช้งานเสมอเพราะเว็บไซต์ไม่สามารถทำงานได้ถ้าไม่มี',
|
||||||
|
categoryFunctional: 'ฟังก์ชั่น',
|
||||||
|
categoryFunctionalDesc: 'คุกกี้การตลาดใช้เพื่อให้เว็บไชต์มีฟังก์ชั่นพิเศษ ถ้าปิดการทำงานอาจจะทำให้บางฟังก์ชั่นของเว็บไซต์ใช้งานไม่ได้',
|
||||||
|
categoryAnalytics: 'การวิเคราะห์',
|
||||||
|
categoryAnalyticsDesc: 'คุกกี้เหล่านี้ช่วยให้เราเข้าใจว่าผู้เข้าชมใช้งานเว็บไซต์อย่างไร เช่น หน้าไหนที่เข้าบ่อย คลิกที่ไหน - ข้อมูลเหล่านี้ช่วยเราพัฒนาเว็บไซต์ให้ดีขึ้นเรื่อยๆ',
|
||||||
|
categoryMarketing: 'การตลาด',
|
||||||
|
categoryMarketingDesc: 'คุกกี้การตลาดใช้ติดตามพฤติกรรมการเข้าชมเว็บไซต์เพื่อแสดงโฆษณาที่เกี่ยวข้องกับความสนใจของคุณบนแพลตฟอร์มอื่นด้วย เช่น Facebook และ Google',
|
||||||
|
categoryPersonalisation: 'ส่วนตัว',
|
||||||
|
categoryPersonalisationDesc: 'คุกกี้การตลาดใช้สำหรับเก็บข้อมูลส่วนตัว',
|
||||||
|
cookieCount: 'มีคุกกี้ {{count}} บนเว็บไซต์นี้',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Built-in translations that don't require an API call. */
|
||||||
|
const BUILT_IN_TRANSLATIONS: Record<string, TranslationStrings> = {
|
||||||
|
en: DEFAULT_TRANSLATIONS,
|
||||||
|
th: THAI_TRANSLATIONS,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the user's preferred locale.
|
* Detect the user's preferred locale.
|
||||||
*
|
*
|
||||||
@@ -105,15 +135,17 @@ export async function fetchTranslations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load translations: try fetching from API, fall back to defaults.
|
* Load translations: use built-in if available, otherwise fetch from API.
|
||||||
*/
|
*/
|
||||||
export async function loadTranslations(
|
export async function loadTranslations(
|
||||||
apiBase: string,
|
apiBase: string,
|
||||||
siteId: string,
|
siteId: string,
|
||||||
locale: string,
|
locale: string,
|
||||||
): Promise<TranslationStrings> {
|
): Promise<TranslationStrings> {
|
||||||
if (locale === 'en') {
|
// Built-in translations (en, th, etc.) — no API call needed
|
||||||
return { ...DEFAULT_TRANSLATIONS };
|
const builtIn = BUILT_IN_TRANSLATIONS[locale];
|
||||||
|
if (builtIn) {
|
||||||
|
return { ...builtIn };
|
||||||
}
|
}
|
||||||
|
|
||||||
const remote = await fetchTranslations(apiBase, siteId, locale);
|
const remote = await fetchTranslations(apiBase, siteId, locale);
|
||||||
|
|||||||
Reference in New Issue
Block a user