# Phase 14: Translation Polish & Testing - Research **Researched:** 2026-01-29 **Domain:** i18n Polish translations, pluralization testing, translation coverage audit **Confidence:** HIGH ## Summary This phase completes Polish translations for all Vue i18n strings and validates the bilingual experience through manual testing and automated pluralization verification. The codebase has 1858 translation keys in both `en.json` and `pl.json` that are currently synchronized, but some Polish translations may be placeholders (English strings as values). The primary work involves: (1) generating a coverage report to identify untranslated strings, (2) reviewing/fixing Polish translations including proper pluralization, (3) adding Vitest unit tests for Polish pluralization rules, and (4) manual walkthrough testing in Polish. Key findings: 1. **Translation files are synchronized** - Both `en.json` and `pl.json` have 1858 keys, but some PL values may be English placeholders 2. **Polish pluralization is configured** - The `i18n.config.ts` has a custom `pluralizationRules.pl` function implementing 4-form rules 3. **18 plural strings exist** - Grep found 18 keys using pipe `|` separator for pluralization 4. **No unit test infrastructure** - The project uses Playwright for E2E but has no Vitest setup for unit tests 5. **CLDR Polish rules are complex** - Polish has 4 forms: one, few, many, other with specific mathematical rules for each **Primary recommendation:** Create a simple Node.js coverage report script (not a full npm package), add Vitest with minimal setup for pluralization unit tests, generate side-by-side review markdown for translation audit, then execute manual walkthrough testing. ## Standard Stack ### Core | Library | Version | Purpose | Why Standard | |---------|---------|---------|--------------| | @nuxt/test-utils | latest | Nuxt test utilities | Official Nuxt testing library | | vitest | latest | Unit test runner | Default for Nuxt 3, fast, TypeScript native | | Node.js fs module | built-in | JSON file comparison | No external deps for simple script | ### Supporting | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | happy-dom | latest | DOM environment for Vitest | Only if testing components (not needed for pure function tests) | ### Alternatives Considered | Instead of | Could Use | Tradeoff | |------------|-----------|----------| | Custom Node script | i18n-check npm | Overkill for 2-file comparison, adds dependency | | Vitest | Jest | Jest requires more config for ESM/TypeScript | | Manual markdown review | Spreadsheet | Markdown stays in repo, version controlled | **Installation:** ```bash cd vue-queststream-app pnpm add -D @nuxt/test-utils vitest ``` ## Architecture Patterns ### Recommended Project Structure ``` vue-queststream-app/ ├── i18n/ │ ├── locales/ │ │ ├── en.json │ │ └── pl.json │ └── i18n.config.ts # Pluralization rules ├── scripts/ │ └── i18n-coverage.ts # Coverage report script ├── tests/ │ └── i18n/ │ └── polish-pluralization.test.ts # Pluralization unit tests └── vitest.config.ts # Vitest configuration ``` ### Pattern 1: Translation Coverage Report Script **What:** Node.js script to compare en.json vs pl.json **When to use:** Before translation review, in CI optionally **Example:** ```typescript // scripts/i18n-coverage.ts import en from '../i18n/locales/en.json' import pl from '../i18n/locales/pl.json' const enKeys = Object.keys(en) const plKeys = Object.keys(pl) // Find keys where PL value equals EN value (potential untranslated) const untranslated = enKeys.filter(key => en[key] === pl[key] && !key.includes('|') // Exclude plurals ) console.log(`Total keys: ${enKeys.length}`) console.log(`Potentially untranslated: ${untranslated.length}`) untranslated.forEach(key => console.log(` - ${key}`)) ``` ### Pattern 2: Polish Pluralization Unit Tests **What:** Test the custom pluralization function with comprehensive values **When to use:** Verify plural form selection is correct for all edge cases **Example:** ```typescript // tests/i18n/polish-pluralization.test.ts import { describe, it, expect } from 'vitest' // Polish pluralization function (extracted from i18n.config.ts) function plPluralRule(choice: number, choicesLength: number): number { if (choice === 0) return 0 const teen = choice > 10 && choice < 20 const endsWithOne = choice % 10 === 1 if (!teen && endsWithOne) return 1 if (!teen && choice % 10 >= 2 && choice % 10 <= 4) return 2 return choicesLength < 4 ? 2 : 3 } describe('Polish pluralization rules', () => { // 4-form tests (one | few | many | other) const testCases = [ { n: 0, expected: 0, form: 'zero' }, { n: 1, expected: 1, form: 'one' }, { n: 2, expected: 2, form: 'few' }, { n: 3, expected: 2, form: 'few' }, { n: 4, expected: 2, form: 'few' }, { n: 5, expected: 3, form: 'many' }, { n: 11, expected: 3, form: 'many (teen)' }, { n: 12, expected: 3, form: 'many (teen)' }, { n: 21, expected: 1, form: 'one (21)' }, { n: 22, expected: 2, form: 'few (22)' }, { n: 25, expected: 3, form: 'many' }, { n: 100, expected: 3, form: 'many' }, { n: 101, expected: 1, form: 'one (101)' }, { n: 102, expected: 2, form: 'few (102)' }, { n: 105, expected: 3, form: 'many' }, ] testCases.forEach(({ n, expected, form }) => { it(`returns form ${expected} (${form}) for n=${n}`, () => { expect(plPluralRule(n, 4)).toBe(expected) }) }) }) ``` ### Pattern 3: Side-by-Side Translation Review File **What:** Markdown table with EN | PL | Context for human review **When to use:** Translation audit workflow **Example:** ```markdown | English | Polish | Context | |---------|--------|---------| | Save Changes | Zapisz zmiany | Button in settings forms | | {count} quest \| {count} quests | {count} misja \| {count} misje \| {count} misji \| {count} misji | Quest count - 4 forms: one\|few\|many\|other | | Hi {name}! | Cześć {name}! | Child dashboard greeting | ``` ### Anti-Patterns to Avoid - **Importing vue-i18n in unit tests:** Test the pluralization function directly, not through i18n runtime - **Using E2E for pluralization:** Playwright is slow for 50+ test cases, use unit tests - **Translating everything at once:** Generate report first, review systematically - **Skipping visual checks:** Polish strings are often 20-30% longer than English ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | JSON key comparison | Full npm package | Simple Node script | 50 lines vs dependency | | Plural rule testing | Browser testing | Vitest unit tests | 100x faster | | Translation review | Manual file comparison | Generated markdown table | Structured, auditable | | Coverage tracking | Spreadsheet | Script output + git diff | Version controlled | **Key insight:** This phase is about validation, not new features. Keep tooling minimal - a script and a test file. ## Common Pitfalls ### Pitfall 1: Testing Pluralization Through vue-i18n Runtime **What goes wrong:** Tests are slow, require full Nuxt context, flaky **Why it happens:** Assumption that i18n must be tested end-to-end **How to avoid:** Extract pluralization function, test as pure function **Warning signs:** Tests taking >100ms each, requiring `mountSuspended` ### Pitfall 2: Missing Polish Plural Forms **What goes wrong:** "5 misja" instead of "5 misji" (wrong form) **Why it happens:** Only providing 2 forms when Polish needs 4 **How to avoid:** Ensure all plural keys have `form0 | form1 | form2 | form3` in pl.json **Warning signs:** Pipe-separated values with different count in EN vs PL ### Pitfall 3: False Positives in Coverage Report **What goes wrong:** Marking valid strings as untranslated **Why it happens:** Some strings are intentionally same in both languages (e.g., "QuestStream", "XP") **How to avoid:** Add exclusion list for proper nouns, abbreviations **Warning signs:** Coverage report showing brand names, technical terms ### Pitfall 4: Overlooking String Length in UI **What goes wrong:** Polish text truncated or breaks layout **Why it happens:** Polish words are longer than English equivalents **How to avoid:** Manual visual walkthrough of all pages in Polish **Warning signs:** Ellipsis (...), text overflow, button text wrapping ### Pitfall 5: Inconsistent Translation Tone **What goes wrong:** Mixed formal/informal Polish ("Ty" vs "Pan/Pani") **Why it happens:** No style guide, different translation sessions **How to avoid:** Review all translations together, enforce consistent tone (informal for QuestStream) **Warning signs:** "Zaloguj się" (formal) mixed with "Cześć!" (informal) ### Pitfall 6: Incorrect Teen Number Handling **What goes wrong:** "11 misja" or "12 misje" (should be "11 misji", "12 misji") **Why it happens:** Polish teen numbers (11-19) use "many" form, not "one"/"few" **How to avoid:** Test values 11-19 explicitly in unit tests **Warning signs:** Grammar errors specifically on 11-19 ## Code Examples ### vitest.config.ts (Minimal Setup) ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { include: ['tests/**/*.{test,spec}.ts'], environment: 'node', // No DOM needed for pure function tests }, }) ``` ### Coverage Report Script ```typescript // scripts/i18n-coverage.ts import { readFileSync } from 'fs' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) interface TranslationFile { [key: string]: string } // Load translation files const en: TranslationFile = JSON.parse( readFileSync(resolve(__dirname, '../i18n/locales/en.json'), 'utf-8') ) const pl: TranslationFile = JSON.parse( readFileSync(resolve(__dirname, '../i18n/locales/pl.json'), 'utf-8') ) // Strings that are intentionally the same in both languages const EXCLUSIONS = [ 'QuestStream', 'XP', 'PIN', // Add brand names, abbreviations, etc. ] const enKeys = Object.keys(en) const plKeys = Object.keys(pl) // Keys in EN but not in PL const missingInPl = enKeys.filter(k => !(k in pl)) // Keys where PL value equals EN value (potential untranslated) const untranslated = enKeys.filter(key => { if (EXCLUSIONS.includes(en[key])) return false if (key.includes('|')) return false // Skip plurals (different structure) return en[key] === pl[key] }) // Plural keys with mismatched form counts const pluralMismatch = enKeys.filter(key => { if (!key.includes('|')) return false const enForms = (en[key].match(/\|/g) || []).length + 1 const plForms = (pl[key].match(/\|/g) || []).length + 1 return enForms !== plForms }) console.log('\n=== i18n Coverage Report ===\n') console.log(`Total keys: ${enKeys.length}`) console.log(`Missing in pl.json: ${missingInPl.length}`) console.log(`Potentially untranslated: ${untranslated.length}`) console.log(`Plural form mismatch: ${pluralMismatch.length}`) if (missingInPl.length > 0) { console.log('\n--- Missing in pl.json ---') missingInPl.forEach(k => console.log(` ${k}`)) } if (untranslated.length > 0) { console.log('\n--- Potentially Untranslated ---') untranslated.slice(0, 20).forEach(k => console.log(` ${k}: "${en[k]}"`)) if (untranslated.length > 20) { console.log(` ... and ${untranslated.length - 20} more`) } } if (pluralMismatch.length > 0) { console.log('\n--- Plural Form Mismatch (EN vs PL) ---') pluralMismatch.forEach(k => { const enForms = (en[k].match(/\|/g) || []).length + 1 const plForms = (pl[k].match(/\|/g) || []).length + 1 console.log(` ${k}: EN has ${enForms} forms, PL has ${plForms} forms`) }) } // Exit code for CI const hasIssues = missingInPl.length > 0 || pluralMismatch.length > 0 process.exit(hasIssues ? 1 : 0) ``` ### Running Coverage Report ```bash npx tsx scripts/i18n-coverage.ts ``` ### Polish Pluralization Test Values Based on CLDR rules, the comprehensive test set for Polish: ```typescript // Test values that cover all plural categories const testValues = [ // Zero form (index 0) 0, // One form (index 1) - ends in 1, not 11 1, 21, 31, 41, 51, 101, 121, // Few form (index 2) - ends in 2-4, not 12-14 2, 3, 4, 22, 23, 24, 32, 33, 34, 102, 103, 104, // Many form (index 3) - everything else including teens 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, // Teens always "many" 20, 25, 26, 27, 28, 29, 30, 100, 105, 111, 112 // Large numbers ] ``` ### Manual Testing Checklist Template ```markdown ## Manual Polish Testing Checklist ### Auth Flow - [ ] /login - All labels, buttons, errors in Polish - [ ] /register - Form labels, validation messages - [ ] Password reset flow ### Parent Dashboard - [ ] /parent - Welcome message, stats, pending approvals - [ ] /parent/children - Child cards, empty state - [ ] /parent/templates - Template library, filters - [ ] /parent/settings - All 7 tabs - [ ] /parent/profile - All 3 tabs ### Child Dashboard - [ ] /child - Hero greeting, stats, quests - [ ] /child/quests - Quest cards, status labels - [ ] /child/shop - Rewards, purchase flow - [ ] /child/achievements - Badges, unlock dates - [ ] /child/profile - Stats, PIN change ### Visual Checks (Polish strings often longer) - [ ] Buttons don't overflow - [ ] Table headers fit - [ ] Modal titles don't truncate - [ ] Navigation labels fit ### Pluralization Live Check - [ ] 1 quest assigned - [ ] 2 quests assigned - [ ] 5 quests assigned - [ ] 21 quests assigned (tricky!) ``` ## State of the Art | Old Approach | Current Approach | When Changed | Impact | |--------------|------------------|--------------|--------| | Jest for Vue | Vitest | 2023 | Faster, better TypeScript support | | @nuxtjs/i18n v8 | @nuxtjs/i18n v10 | 2025 | Better lazy loading, bundle optimization | | Manual key audit | Automated coverage scripts | Always | Catch regressions in CI | **Deprecated/outdated:** - `vue-i18n-jest`: Use `vitest` directly - `nuxt-vitest`: Merged into `@nuxt/test-utils` ## Open Questions 1. **Console Warning Logging** - What we know: vue-i18n can log fallback warnings - What's unclear: How to capture warnings during manual testing - Recommendation: Set `missingWarn: true` in i18n.config.ts during Polish testing, watch browser console 2. **CI Integration Scope** - What we know: Coverage script can exit with error code - What's unclear: Should this block deploys or just warn? - Recommendation: Start as warning, consider blocking after Phase 14 complete ## Polish Pluralization Rules Reference Based on CLDR (Unicode Common Locale Data Repository): | Category | Condition | Examples | Form Index | |----------|-----------|----------|------------| | **zero** | n = 0 | 0 | 0 | | **one** | n % 10 = 1 AND n % 100 != 11 | 1, 21, 31, 101 | 1 | | **few** | n % 10 in 2..4 AND n % 100 not in 12..14 | 2, 3, 4, 22, 23, 24 | 2 | | **many** | n % 10 = 0 OR n % 10 in 5..9 OR n % 100 in 11..14 | 0, 5, 10, 11, 12, 13, 14, 15, 20, 25 | 3 | Example translations: ``` 1 misja, 2 misje, 5 misji, 11 misji, 21 misja, 22 misje 1 dzień, 2 dni, 5 dni, 11 dni, 21 dzień, 22 dni 1 dziecko, 2 dzieci, 5 dzieci, 11 dzieci, 21 dziecko, 22 dzieci ``` ## Sources ### Primary (HIGH confidence) - Project codebase: `vue-queststream-app/i18n/locales/*.json` - verified key counts - Project codebase: `vue-queststream-app/i18n/i18n.config.ts` - verified pluralization rule - [Nuxt Testing Documentation](https://nuxt.com/docs/getting-started/testing) - Vitest setup - [Vue I18n Pluralization](https://vue-i18n.intlify.dev/guide/essentials/pluralization) - Pipe syntax ### Secondary (MEDIUM confidence) - [CLDR Plural Rules](https://cldr.unicode.org/index/cldr-spec/plural-rules) - Polish rule reference - Phase 13 RESEARCH.md - established i18n patterns - Phase 13 SUMMARY files - translation string counts ### Tertiary (LOW confidence) - WebSearch for testing tools - validation needed during implementation ## Metadata **Confidence breakdown:** - Standard stack: HIGH - verified with official Nuxt docs - Architecture patterns: HIGH - patterns verified with codebase - Pluralization rules: HIGH - verified with CLDR reference - Testing approach: MEDIUM - Vitest setup needs validation during implementation - Pitfalls: HIGH - based on Phase 13 learnings **Research date:** 2026-01-29 **Valid until:** 90 days (stable patterns, minimal ecosystem churn expected)