diff --git a/e2e/app.spec.ts b/e2e/app.spec.ts index 81f80a4..d8907e1 100644 --- a/e2e/app.spec.ts +++ b/e2e/app.spec.ts @@ -161,7 +161,7 @@ test.describe('Core User Flows', () => { await expect(page.getByRole('button', { name: /crear nuevo juego|create new game/i })).toBeVisible() }) - test('should support multiple languages including German and Dutch', async ({ page }) => { + test('should support multiple languages including Korean, German, and Dutch', async ({ page }) => { await page.goto('/') // Verify language toggle is available (use aria-haspopup to target dropdown trigger specifically) @@ -169,7 +169,7 @@ test.describe('Core User Flows', () => { await expect(languageButton).toBeVisible() // Verify welcome description is visible (use paragraph element to avoid matching button text) - const welcomeDesc = page.locator('p').getByText(/organize|organiza|organisez|organizza|ギフト|轻松|organisieren|organiseer/i) + const welcomeDesc = page.locator('p').getByText(/organize|organiza|organisez|organizza|ギフト|쉽고 재미있게|轻松|organisieren|organiseer/i) await expect(welcomeDesc).toBeVisible() }) @@ -201,6 +201,13 @@ test.describe('Core User Flows', () => { await enPage.goto('/?lang=en') await expect(enPage.getByRole('button', { name: /create new game/i })).toBeVisible() await enContext.close() + + // Test Korean + const koContext = await browser.newContext() + const koPage = await koContext.newPage() + await koPage.goto('/?lang=ko') + await expect(koPage.getByRole('button', { name: /새 게임 만들기/i })).toBeVisible() + await koContext.close() }) test('should navigate directly to guide pages via URL parameter', async ({ page }) => { diff --git a/src/lib/api.ts b/src/lib/api.ts index 05b4500..4fc196d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -80,7 +80,7 @@ export interface CreateGameData { organizerEmail?: string participants: Array<{ name: string; email?: string; desiredGift: string; wish: string }> sendEmails?: boolean - language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' + language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' } export interface CreateGameResponse extends Game { @@ -128,7 +128,7 @@ export async function updateGameAPI( code: string, action: 'requestReassignment', participantId: string, - language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' + language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' ): Promise { const response = await fetch(`${API_BASE_URL}/games/${code}`, { method: 'PATCH', @@ -244,7 +244,7 @@ export async function updateWishAPI( code: string, participantId: string, wish: string, - language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' + language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' ): Promise { const response = await fetch(`${API_BASE_URL}/games/${code}`, { method: 'PATCH', @@ -271,7 +271,7 @@ export async function updateParticipantEmailAPI( code: string, participantId: string, email: string, - language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' + language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' ): Promise { const response = await fetch(`${API_BASE_URL}/games/${code}`, { method: 'PATCH', @@ -332,7 +332,7 @@ export async function updateParticipantDetailsAPI( export async function confirmAssignmentAPI( code: string, participantId: string, - language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' + language?: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' ): Promise { const response = await fetch(`${API_BASE_URL}/games/${code}`, { method: 'PATCH', @@ -546,7 +546,7 @@ export interface SendEmailResponse { export async function sendOrganizerEmailAPI( code: string, organizerToken: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es' + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es' ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { method: 'POST', @@ -598,7 +598,7 @@ export async function sendParticipantEmailAPI( export async function sendAllParticipantEmailsAPI( code: string, organizerToken: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es' + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es' ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { method: 'POST', @@ -625,7 +625,7 @@ export async function sendReminderEmailAPI( code: string, organizerToken: string, participantId: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es', + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es', customMessage?: string ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { @@ -654,7 +654,7 @@ export async function sendReminderEmailAPI( export async function sendReminderToAllAPI( code: string, organizerToken: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es', + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es', customMessage?: string ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { @@ -716,7 +716,7 @@ export interface RecoverOrganizerLinkResponse { export async function recoverOrganizerLinkAPI( code: string, email: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es' + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es' ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { method: 'POST', @@ -750,7 +750,7 @@ export async function recoverOrganizerLinkAPI( export async function recoverParticipantLinkAPI( code: string, email: string, - language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' = 'es' + language: 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' = 'es' ): Promise { const response = await fetch(`${API_BASE_URL}/email/send`, { method: 'POST', diff --git a/src/lib/translations/README.md b/src/lib/translations/README.md index fc81dd6..c19c672 100644 --- a/src/lib/translations/README.md +++ b/src/lib/translations/README.md @@ -12,6 +12,7 @@ Each language has its own file: - `fr.ts` - French (Français) - `it.ts` - Italian (Italiano) - `ja.ts` - Japanese (日本語) +- `ko.ts` - Korean (한국어) - `zh.ts` - Chinese (中文) - `de.ts` - German (Deutsch) - `nl.ts` - Dutch (Nederlands) diff --git a/src/lib/translations/index.ts b/src/lib/translations/index.ts index 7572df1..1bb4052 100644 --- a/src/lib/translations/index.ts +++ b/src/lib/translations/index.ts @@ -4,6 +4,7 @@ import { pt } from './pt' import { fr } from './fr' import { it } from './it' import { ja } from './ja' +import { ko } from './ko' import { zh } from './zh' import { de } from './de' import { nl } from './nl' @@ -15,6 +16,7 @@ export const translations = { fr, it, ja, + ko, zh, de, nl diff --git a/src/lib/translations/ko.ts b/src/lib/translations/ko.ts new file mode 100644 index 0000000..c0642c9 --- /dev/null +++ b/src/lib/translations/ko.ts @@ -0,0 +1,26 @@ +import { en } from './en' + +export const ko = { + ...en, + appName: 'Zava 선물 교환', + welcome: '환영합니다!', + welcomeDesc: '쉽고 재미있게 선물 교환을 준비하세요', + createGame: '새 게임 만들기', + joinGame: '게임 참가하기', + enterCode: '코드를 입력하세요', + codePlaceholder: '6자리 코드', + continue: '계속', + back: '뒤로', + next: '다음', + finish: '완료', + cancel: '취소', + confirm: '확인', + language: '언어', + darkMode: '다크 모드', + lightMode: '라이트 모드', + privacyLink: '개인정보 처리방침', + guideOrganizerLink: '주최자 가이드', + guideParticipantLink: '참가자 가이드', + guideOrganizerTitle: '주최자 가이드', + guideParticipantTitle: '참가자 가이드' +} diff --git a/src/lib/types.ts b/src/lib/types.ts index d457918..8e08782 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -80,7 +80,7 @@ export interface Game { archivedAt?: number // Unix timestamp in milliseconds since epoch when the game was archived (Date.now()) } -export type Language = 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'zh' | 'de' | 'nl' +export type Language = 'en' | 'es' | 'pt' | 'fr' | 'it' | 'ja' | 'ko' | 'zh' | 'de' | 'nl' export interface LanguageOption { code: Language @@ -96,6 +96,7 @@ export const LANGUAGES: LanguageOption[] = [ { code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' }, { code: 'it', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' }, { code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' }, + { code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' }, { code: 'zh', name: 'Chinese', nativeName: '中文', flag: '🇨🇳' }, { code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪' }, { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱' },