# Localization

## Overview

This document describes the translation process, tools, and current status of all language translations for Fulguris Web Browser.

## Status

### Languages

| Flag | Language | Code | Issues | Completion | Status | Notes |
|------|----------|------|--------|------------|--------|-------|
| 🇿🇦 | **Afrikaans** | af-rZA | 554 | ~5% | 🔴 | Almost complete retranslation needed |
| 🇸🇦 | **Arabic** | ar-rSA | 1 | ~99% | ✅ | Format placeholder only |
| 🇧🇦 | **Bosnian** | bs-rBA | 14 | ~98% | ✅ | Near-match punctuation issues |
| 🇪🇸 | **Catalan** | ca-rES | 555 | ~5% | 🔴 | Almost complete retranslation needed |
| 🇨🇳 | **Chinese (CN)** | zh-rCN | 1 | ~99% | ✅ | Format placeholder only |
| 🇹🇼 | **Chinese (TW)** | zh-rTW | ~10 | ~98% | ✅ | Fully translated |
| 🇭🇷 | **Croatian** | hr-rHR | 74 | ~87% | 🟡 | Layer types, SSL errors, panel strings |
| 🇨🇿 | **Czech** | cs-rCZ | 417 | ~28% | 🟠 | Major work needed |
| 🇩🇰 | **Danish** | da-rDK | 33 | ~94% | ✅ | SSL strings fixed - Production ready! |
| 🇳🇱 | **Dutch** | nl-rNL | 18 | ~97% | ✅ | Near-match punctuation issues |
| 🇫🇮 | **Finnish** | fi-rFI | <10 | ~98% | ✅ | 550+ strings translated |
| 🇫🇷 | **French** | fr-rFR | 35 | ~94% | ✅ | Near-match punctuation issues |
| 🇩🇪 | **German** | de-rDE | 37 | ~94% | ✅ | Near-match punctuation issues |
| 🇬🇷 | **Greek** | el-rGR | ~10 | ~98% | ✅ | Fully translated |
| 🇮🇱 | **Hebrew** | iw-rIL | 550 | ~6% | 🔴 | Almost complete retranslation needed |
| 🇭🇺 | **Hungarian** | hu-rHU | 9 | ~98% | ✅ | Near-match punctuation issues |
| 🇮🇩 | **Indonesian** | in-rID | 31 | ~95% | ✅ | Technical terms kept in English |
| 🇮🇹 | **Italian** | it-rIT | 38 | ~93% | ✅ | Near-match punctuation issues |
| 🇯🇵 | **Japanese** | ja-rJP | 2 | ~99% | ✅ | Format placeholders only |
| 🇰🇷 | **Korean** | ko-rKR | 1 | ~99% | ✅ | Format placeholder only |
| 🇱🇹 | **Lithuanian** | lt-rLT | 533 | ~9% | 🔴 | Almost complete retranslation needed |
| 🇲🇪 | **Montenegro** | me-rME | 484 | ~17% | 🟠 | Extensive translation needed |
| 🇳🇴 | **Norwegian** | no-rNO | 18 | ~97% | ✅ | Production ready! Major translation completed Nov 2025 |
| 🇵🇱 | **Polish** | pl-rPL | 9 | ~98% | ✅ | Near-match punctuation issues |
| 🇧🇷 | **Portuguese (BR)** | pt-rBR | 26 | ~96% | ✅ | Near-match punctuation issues |
| 🇵🇹 | **Portuguese (PT)** | pt-rPT | 23 | ~96% | ✅ | Near-match punctuation issues |
| 🇷🇴 | **Romanian** | ro-rRO | 32 | ~94% | ✅ | Production ready! Major translation completed Nov 2025 |
| 🇷🇺 | **Russian** | ru-rRU | 1 | ~99% | ✅ | Format placeholder only |
| 🇮🇳 | **Santali** | sat-rIN | 30 | ~95% | ✅ | Production ready! Proper Ol Chiki script - Nov 2025 |
| 🇷🇸 | **Serbian (Cyrillic)** | sr-rCS | 75 | ~87% | 🟡 | Layer types, SSL errors, panel strings |
| 🇷🇸 | **Serbian (Latin)** | sr-rSP | 62 | ~89% | ✅ | Minor issues remaining |
| 🇪🇸 | **Spanish** | es-rES | 21 | ~96% | ✅ | Near-match punctuation issues |
| 🇸🇪 | **Swedish** | sv-rSE | ~10 | ~98% | ✅ | Fully translated |
| 🇹🇭 | **Thai** | th-rTH | 1 | ~99.8% | ✅ | Format placeholder only - Production ready! |
| 🇹🇷 | **Turkish** | tr-rTR | 2 | ~99% | ✅ | Format placeholders only |
| 🇺🇦 | **Ukrainian** | uk-rUA | 1 | ~99% | ✅ | Format placeholder only |
| 🇻🇳 | **Vietnamese** | vi-rVN | 1 | ~99% | ✅ | Format placeholder only |

**Last Updated:** November 14, 2025

### Legend
- ✅ **Complete** (95-100%) - Production ready
- 🟡 **High Quality** (85-94%) - Minor work needed
- 🟠 **Needs Work** (15-84%) - Significant translation required
- 🔴 **Minimal** (0-14%) - Almost complete retranslation needed

**Total Languages:** 37  
**Languages with ≤10 issues:** 21 (57%)  
**Languages with 11-70 issues:** 15 (41%)  
**Languages with 71-200 issues:** 0 (0%)  
**Languages with >500 issues:** 10 (27%)  
**Languages 90%+ Complete:** 25 (68%)

## Translation Tool: `l10n.py`

The `l10n.py` script is a comprehensive Python-based localization tool that manages all translation operations for the project.

### Key Features

- **Batch Translation**: Update multiple strings at once
- **Strict Update Mode**: `--set` only updates existing strings to prevent accidental additions
- **Synchronized Addition**: `--add` command adds new strings to ALL languages to keep files in sync
- **Windows Line Endings**: Detects and preserves CRLF (`\r\n`) line endings on Windows
- **Quality Checking**: Detect untranslated strings, near-match issues, and **CRITICAL placeholder mismatches** that cause app crashes
- **Plural Support**: Handle complex plural forms for different languages
- **XML Handling**: Support for complex XML with `<xliff:g>` tags
- **Near-Match Detection**: Find strings differing by 1-2 characters (punctuation, typos)
- **Automated Operations**: Add/remove strings across all language files

### Translation Process

**Note:** All commands should be run in your existing terminal session on Windows using PowerShell. Do not spawn new terminal windows.

#### 1. Check Translation Status

```bash
# Check all languages (shows first 20 issues per language)
python l10n.py --check

# Check specific language (shows ALL issues)
python l10n.py --check ko-rKR

# Check with near-match detection (finds similar untranslated strings)
python l10n.py --check ko-rKR --near 1
```

**The check command automatically validates:**
- Untranslated strings (matching English exactly)
- Near-match strings (1-2 character differences, possibly punctuation errors)
- **[CRITICAL] Placeholder mismatches** - Missing or incorrect format placeholders like `%s`, `%1$s`, `%d` that **WILL cause app crashes**

**Example output for critical issues:**
```
fi-rFI (8 issues, 3 CRITICAL):
  [CRITICAL] Placeholder mismatch: dialog_title_incoming_url
    English: ['%1$s']
    Translation: []
    WARNING: This WILL cause app crashes!
```

Always fix CRITICAL placeholder issues immediately - these cause runtime crashes!

#### 2. Translate Strings

**⚠️ IMPORTANT: Adding vs Updating Strings**

The `--set` command only updates existing strings. If a string doesn't exist, it will error - this is by design.

**During normal translation work:** You should NEVER encounter "string does not exist" errors. If you do, it means either:
- The string doesn't exist in the English source file (`values/strings.xml`)
- The language file is out of sync with the English source

In either case, **STOP and investigate** before attempting to add strings.

**Update Existing String (Normal Translation Workflow):**
```bash
python l10n.py --set ko-rKR settings_title '설정'
# Updates existing string, or errors if it doesn't exist
```

**Add New String to All Languages (Developers Only):**
```bash
python l10n.py --add settings_title 'Settings'
# Adds the string to ALL language files (values/strings.xml and all values-*/strings.xml)
# Use this when adding a completely new string resource
```

**Multiple Strings (Batch Update):**
```bash
python l10n.py --set ko-rKR enable '사용' disable '사용 안 함' settings '설정'
# Updates multiple existing strings at once
```

**Complex XML Strings (with --raw flag):**
```bash
python l10n.py --raw --set ko-rKR match_x_of_n '<xliff:g id="current_match">%1$d</xliff:g>개 중'
```

#### 3. Add or Remove Strings Across All Languages (Developers Only)

**⚠️ These commands are for developers managing string resources, NOT for translators.**

```bash
# Add a new string to all language files
python l10n.py --add new_feature_name 'New Feature'

# Remove a string from all language files
python l10n.py --remove obsolete_string_id
```

After adding a new string, translators should use `--set` to translate it in each language.

#### 4. Handle Plurals

```bash
# Get current plural values
python l10n.py --get-plurals ko-rKR notification_incognito_running_title

# Set plural values
python l10n.py --set-plurals ko-rKR settings_summary_cookies \
  zero '현재 페이지에 쿠키가 없습니다' \
  one '현재 페이지에 쿠키가 1개 있습니다' \
  other '현재 페이지에 쿠키가 %d개 있습니다'
```

#### 5. Get String Values

```bash
python l10n.py --get ko-rKR settings_title
```

### Exclusion Rules

The check tool automatically excludes certain strings from being flagged as untranslated:

**International Terms** (no translation needed):
- `WebView`, `Android`, `iOS`, `Linux`, `macOS`, `Windows`, `Desktop`, `Mobile`
- `JavaScript`, `Cookies`, `Port:`, `LeakCanary`

**String Prefixes** (technical identifiers):
- `agent_*` - User agent strings (e.g., `agent_android_mobile`)
- `log_level_*` - Log level names (e.g., `log_level_warn`)

**Special Strings**:
- `@string/...` - String references
- `android_open_source_project`, `jsoup`, `infinity`, `search_action` - Proper nouns

**Short Strings**:
- Strings with ≤3 characters are excluded from checks

### PowerShell Quoting Tips

When using PowerShell, use single quotes for outer container and backslash to escape inner double quotes:

```powershell
# Simple strings - use single quotes
python l10n.py --set ko-rKR dialog_title '설정'

# Strings with placeholders - single quotes work perfectly, $ is literal
python l10n.py --set ko-rKR dialog_title '%1$s 열기'

# Strings with double quotes - escape with backslash
python l10n.py --set ko-rKR session_switched '전환됨 \"%s\"'

# Strings with apostrophes (single quotes) - double them up
python l10n.py --set en-rUS action_dont_allow 'Don''t allow'
python l10n.py --set en-rUS save_data 'Request ''Save-Data'''

# Complex XML strings - single quotes with backslash-escaped quotes
python l10n.py --raw --set ko-rKR test 'Text <xliff:g id=\"example\">%1$d</xliff:g>개'

# Alternative for complex XML - use here-strings (optional)
$value = @'
<xliff:g id="example">%1$d</xliff:g>개
'@
python l10n.py --raw --set ko-rKR test $value
```

**Why single quotes work:**
- PowerShell single quotes are literal strings (no variable expansion)
- Dollar signs `$` in placeholders like `%1$s` work perfectly - no escaping needed
- Backslash `\"` escapes inner double quotes - PowerShell passes `\` to Python, which receives actual quotes
- **Apostrophes (single quotes) must be doubled:** `''` → PowerShell converts to `'` that Python receives
- Newlines `\n` stay literal (not interpreted)

## Android String Resource Validation

The `l10n.py` script performs comprehensive validation of all string content **before** writing to XML files. This catches errors that would break Android resource compilation.

### Validation Rules

The script validates **ALL** strings (with or without `--raw` flag):

#### Invalid Entity References
- ❌ `&pos;` - Common typo, breaks compilation → Use `\'`
- ❌ `&apos;` - WRONG for Android XML, breaks compilation → Use `\'`  
- ❌ `&quot;` - Wrong for Android → Use `\"`
- ✅ `&amp;` `&lt;` `&gt;` - Valid XML entities
- ✅ `&#123;` `&#x1F4A1;` - Numeric entities are valid

#### XML Structure
- ❌ Unquoted attributes: `id=value` → Must be `id="value"`
- ❌ Unclosed tags: `<xliff:g>text` → Must have `</xliff:g>`
- ❌ Mismatched tags: `<xliff:g></b>` → Tags must match

#### Escape Requirements
- ✅ Apostrophes: Use `\'` (backslash-escape)
- ✅ Quotes: Use `\"` (backslash-escape)  
- ❌ Do NOT use XML entities like `&apos;` or `&quot;`

### Example Validation Errors

```bash
# Invalid entity - will be rejected
python l10n.py --set el-rGR test "Don&apos;t"
# ERROR: Entity '&apos;' detected! Use \' instead.

# Invalid typo - will be rejected  
python l10n.py --set el-rGR test "Test &pos; here"
# ERROR: Invalid entity '&pos;' detected! Use \' for apostrophes.

# Unquoted attribute - will be rejected
python l10n.py --raw --set el-rGR test '<xliff:g id=bad>%s</xliff:g>'
# ERROR: Attribute value not quoted: id=bad
```

### What Gets Validated

- **Regular `--set`**: Validates THEN escapes automatically
- **With `--raw` flag**: Validates WITHOUT escaping (you provide pre-escaped content)

Both modes validate to prevent broken XML from reaching Android compilation.

### ⚠️ CRITICAL: PowerShell XML Quote Escaping

**Use single quotes with backslash-escaped double quotes for XML attributes!**

The l10n.py script validates ALL XML content (with or without `--raw`) and will reject malformed XML before it breaks Android compilation.

✅ **CORRECT - Use single quotes with backslash escaping:**
```powershell
# Single quotes, escape inner quotes with backslash
python l10n.py --raw --set el-rGR test 'Text <xliff:g id=\"session_name\" example=\"News\">%s</xliff:g>'
```

✅ **CORRECT - Use PowerShell here-string (alternative, cleaner for complex XML):**
```powershell
$value = @'
Text <xliff:g id="session_name" example="News">%s</xliff:g>
'@
python l10n.py --raw --set el-rGR message_session_imported $value
```

✅ **CORRECT - For simple strings without XML tags:**
```powershell
# Regular --set automatically escapes quotes and apostrophes
python l10n.py --set el-rGR message "This is a simple message"
```

❌ **WRONG - These break:**
```powershell
# Double quotes with backslash - PowerShell passes backslashes literally
python l10n.py --raw --set el-rGR test "text <xliff:g id=\"x\">%s</xliff:g>"
# Result: <xliff:g id=\"x\"> (backslashes in XML = error)

# Single quotes without escaping - missing quotes around attribute value
python l10n.py --raw --set el-rGR test 'text <xliff:g id=x>%s</xliff:g>'
# Result: <xliff:g id=x> (missing quotes = XML error - WILL BE REJECTED)
```

**Common XML validation errors detected:**
- ❌ Backslashes in XML attributes (`id=\"value\"`) → Use single quotes with `\"` or here-strings
- ❌ Missing quotes around attributes (`id=value`) → Must use quotes (`id="value"`)
- ❌ Unclosed or mismatched XML tags
- ❌ Malformed XML structure

**Why single quotes with backslash work:**
- PowerShell single quotes treat everything literally
- Backslash `\` is passed through to Python as-is
- Python script receives `\"` and converts it to actual `"` in the XML
- Result: Proper XML with quoted attributes `id="value"`

**Best practice for complex XML:**
1. **Use single quotes with `\"`** - Simple and works for most cases
2. **Use PowerShell here-strings** (the `@'...'@` syntax) - Cleaner for very complex XML
3. **Use regular `--set`** for simple strings without XML tags - automatic escaping

### ⚠️ CRITICAL: Apostrophe Escaping in Android XML

**Android XML string resources have specific escaping requirements that differ from standard XML:**

- ✅ **CORRECT:** Use `\'` to escape apostrophes
  ```xml
  <string name="example">Don\'t use &apos; here</string>
  ```

- ❌ **WRONG:** Using `&apos;` will cause **build failures**
  ```xml
  <!-- This will FAIL to compile! -->
  <string name="example">Don&apos;t use this</string>
  ```

**The l10n.py tool automatically detects and fixes `&apos;` issues:**
- When you use `--set` with a value containing `&apos;`, it will:
  1. Display a warning
  2. Automatically convert `&apos;` to `\'`
  3. Show you the before/after values

**Example:**
```bash
python l10n.py --set vi-rVN do_not_track "Yêu cầu &apos;Không theo dõi&apos;"
```
Output:
```
[WARNING] Detected &apos; entity - this is INCORRECT for Android XML!
          Converting &apos; to \' (proper Android XML escape)
          Original value: Yêu cầu &apos;Không theo dõi&apos;
          Fixed value: Yêu cầu 'Không theo dõi'
[OK] Successfully updated vi-rVN:do_not_track
```

### ⚠️ CRITICAL: Placeholder and Format Specifier Requirements

**Format specifiers MUST match exactly between English and translations. This is enforced by the `--check` command.**

**Why this is critical:**
- **Missing placeholders** → Text displays incorrectly (e.g., "Downloaded" instead of "Downloaded file.pdf")
- **Extra placeholders** → App crashes at runtime with format exceptions
- **Wrong placeholder order** → Wrong data in wrong position (e.g., "10 of 1" instead of "1 of 10")

**Common format specifiers:**
- `%s` - String placeholder
- `%d` - Integer placeholder
- `%1$s`, `%2$s` - Positional string placeholders
- `%1$d`, `%2$d` - Positional integer placeholders
- `<xliff:g id="name">%s</xliff:g>` - XML-tagged placeholders (must preserve entire tag)

**Examples of CORRECT translations:**

✅ **Simple placeholder:**
```bash
# English: "Downloaded %s"
python l10n.py --set th-rTH string_id 'ดาวน์โหลด %s แล้ว'
# ✅ Has exactly one %s
```

✅ **Multiple positional placeholders:**
```bash
# English: "Match %1$d of %2$d"
python l10n.py --set th-rTH string_id 'ตรงกัน %1$d จาก %2$d'
# ✅ Has both %1$d and %2$d in any order
```

✅ **XML-tagged placeholders:**
```bash
# English: 'Match <xliff:g id="current">%1$d</xliff:g> of <xliff:g id="total">%2$d</xliff:g>'
python l10n.py --raw --set th-rTH string_id 'ตรงกัน <xliff:g id=\"current\">%1$d</xliff:g> จาก <xliff:g id=\"total\">%2$d</xliff:g>'
# ✅ Both XML tags and placeholders preserved
```

**Examples of WRONG translations (will be flagged by --check):**

❌ **Missing placeholder:**
```bash
# English: "Downloaded %s" (1 placeholder)
python l10n.py --set th-rTH string_id 'ดาวน์โหลดแล้ว'
# ❌ ERROR: Placeholder mismatch - missing %s
```

❌ **Extra placeholder:**
```bash
# English: "Downloaded" (0 placeholders)
python l10n.py --set th-rTH string_id 'ดาวน์โหลด %s แล้ว'
# ❌ ERROR: Placeholder mismatch - unexpected %s
```

❌ **Wrong placeholder type:**
```bash
# English: "Downloaded %d files" (%d = integer)
python l10n.py --set th-rTH string_id 'ดาวน์โหลด %s ไฟล์'
# ❌ ERROR: Placeholder mismatch - %s instead of %d
```

**The --check command will flag these as critical errors:**
```
th-rTH (1 issues):
  Placeholder mismatch: string_id
    English: ['%1$s', '%2$d']
    Translation: ['%1$s']
```

**Important rules:**
1. **Count must match** - Same number of placeholders in English and translation
2. **Types must match** - `%s` stays `%s`, `%d` stays `%d`, etc.
3. **IDs must match** - `%1$s` stays `%1$s` (can reorder, but IDs stay same)
4. **XML tags must be preserved** - Complete `<xliff:g>...</xliff:g>` structures
5. **Always run `--check`** before submitting translations to catch these errors


## Common Untranslated Strings

These strings are frequently untranslated across multiple languages:

### Format Placeholders
- `dialog_message_incoming_url` = `'%1$s\n\n%2$s'` - Intentionally left as-is

### User Agent Strings (Intentionally Untranslated)
- `agent_android_mobile` = `'Android (Mobile)'`
- `agent_ios_mobile` = `'iOS (Mobile)'`
- `agent_linux_desktop` = `'Linux (Desktop)'`
- `agent_macos_desktop` = `'macOS (Desktop)'`
- `agent_windows_desktop` = `'Windows (Desktop)'`

### Frequently Missing Translations
1. **Introduction/Settings:**
   - `settings_title_introduction` = `'Introduction'`
   - `settings_title_delete_configuration` = `'Delete configuration'`
   - `settings_title_add_configuration` = `'Add configuration'`

2. **Accent Colors:**
   - `accent_color`, `accent_amber`, `accent_blue`, `accent_cyan`, etc.

3. **Layer Types:**
   - `layer_type_none` = `'None'`
   - `layer_type_software` = `'Software - WebGL off'`
   - `layer_type_hardware` = `'Hardware - WebGL on'`

4. **SSL Information:**
   - `ssl_info_issued_by` = `'Issued by:'`
   - `ssl_info_issued_to` = `'Issued to:'`
   - `ssl_info_issued_on` = `'Issued on:'`
   - `ssl_info_expires_on` = `'Expires on:'`

5. **Session Management:**
   - `label_session` = `'Session'`
   - `session_recovery` = `'Recovery'`
   - `session_name_prompt` = `'Enter session name:'`

## Translation Quality Metrics

### Total Strings
- **Regular Strings:** 583
- **Plural Resources:** 2
  - `notification_incognito_running_title` (incognito tab count)
  - `settings_summary_cookies` (cookie count)

### Completion Criteria

A language is considered "complete" when:
1. All translatable strings are translated (excluding proper nouns)
2. All plural forms are properly handled
3. No placeholder mismatches exist
4. Near-match issues are resolved (punctuation, typos)

### Quality Checks Performed

1. **Untranslated Detection:** Exact match with English text
2. **Near-Match Detection:** 1-2 character differences (punctuation, typos)
3. **⚠️ Placeholder Consistency (CRITICAL):** Verify `%s`, `%1$s`, `%d`, `%1$d`, `<xliff:g>` tags match exactly
   - **Missing placeholders break the app** - text won't display correctly
   - **Extra placeholders crash the app** - causes runtime exceptions
   - All format specifiers must match between English and translation
   - This check is always performed and failures are flagged as critical errors
4. **Plural Completeness:** Check all required quantities are translated
5. **XML Validation:** Ensure proper XML structure and escaping

## Maintenance Tasks

### Adding New Strings

```bash
# Add a new string to all language files
python l10n.py --add new_feature_name "New Feature"
```

### Removing Obsolete Strings

```bash
# Remove a string from all language files
python l10n.py --remove obsolete_string_id
```

### Listing All Languages

```bash
python l10n.py --list
```

## Best Practices

### For Translators

1. **Use Native Speakers:** Always prefer native speakers for translation quality
2. **Context Matters:** Understand the UI context where strings appear
3. **Consistency:** Use consistent terminology throughout translations
4. **Plural Forms:** Ensure proper plural forms for your language
5. **Format Preservation:** Maintain `%s`, `%1$d`, `<xliff:g>` placeholders exactly
6. **Cultural Appropriateness:** Adapt idioms and phrases culturally
7. **Report Missing Strings:** If you encounter "string does not exist" errors during translation, **STOP and report to developers**. Do NOT attempt to add strings yourself - this indicates the language file is out of sync with the English source

### For Developers

1. **Check Before Commit:** Run `python l10n.py --check` before committing
2. **Update All Languages:** Use `--add` to add new strings to all languages
3. **Document Context:** Add comments in English strings.xml for context
4. **Test RTL Languages:** Test Arabic, Hebrew for right-to-left display
5. **Validate XML:** Ensure proper XML escaping for special characters

## Technical Details

### String Escaping Rules

**⚠️ Android XML Resources vs Standard XML:**

Android uses a **modified XML format** for string resources that has different escaping rules than standard XML:

**Regular Strings** (`--set`):
- Apostrophes (`'`) → `\'` ✅ **REQUIRED** (not `&apos;` ❌)
- Quotes (`"`) → `\"` ✅ **REQUIRED** (not `&quot;` ❌)
- Ampersands (`&`) → `&amp;` (standard XML entity)
- Less than (`<`) → `&lt;` (unless part of XML tag like `<xliff:g>`)
- Greater than (`>`) → `&gt;` (unless part of XML tag)

**Why `&apos;` doesn't work:**
- Standard XML allows `&apos;` as a character entity
- Android's resource compiler does **NOT** accept `&apos;` in string values
- Using `&apos;` will cause: `Failed to compile values resource file` error
- Solution: Always use `\'` for apostrophes in Android string resources

**The l10n.py tool protects you:**
- Automatically detects `&apos;` and `&quot;` in your input
- Displays warnings when these incorrect entities are found
- Converts them to the correct `\'` and `\"` escapes
- Prevents build failures before they happen

**Raw Strings** (`--raw` flag with `--set`):
- Use: `python l10n.py --raw --set <lang> <id> <value>`
- No automatic escaping
- Used for complex XML with `<xliff:g>`, `<b>` tags
- Developer must handle escaping manually
- Still subject to Android's `\'` requirement (not `&apos;`)
- All content is still validated for XML correctness

### Plural Forms by Language

Different languages have different plural rules:

- **English, German, Dutch:** `one`, `other`
- **French, Portuguese:** `one`, `other`
- **Russian, Ukrainian:** `one`, `few`, `many`, `other`
- **Arabic:** `zero`, `one`, `two`, `few`, `many`, `other`
- **Korean, Japanese, Chinese:** `other` only (no plural distinction)
- **Polish:** `one`, `few`, `many`, `other`

## Known Issues

1. **Format Placeholder String:** `dialog_message_incoming_url` shows as "untranslated" in all languages - this is intentional as it's just `'%1$s\n\n%2$s'`

2. **Near-Match Default:** The tool defaults to `--near 1` to catch punctuation differences

3. **XML Encoding Issues:** Some language files may have encoding issues visible in logs (displayed as `��` characters) but files are correctly UTF-8 encoded

## Future Improvements

1. **Crowdin Integration:** Document integration with Crowdin for community translations
2. **Translation Memory:** Build translation memory for consistency
3. **Automated Testing:** Add unit tests for translation completeness
4. **Context Screenshots:** Provide screenshots showing where strings appear in UI
5. **Glossary:** Create terminology glossary for consistent translations

## Support

For translation questions or issues:
1. Check this documentation first
2. Run `python l10n.py --help` for command reference
3. Test translations with `python l10n.py --check <lang>`
4. Open an issue on GitHub with the `l10n` label

---

## Regression Testing

### Overview

The `l10n.py` script includes comprehensive Android string resource validation. These tests should be run after any changes to the script or to validate it's working correctly.

### Quick Validation Test Suite

Run these commands to verify all functionality:

```powershell
# 1. Test help displays correctly
python l10n.py --help

# 2. Test language list
python l10n.py --list

# 3. Test check command (should complete without errors)
python l10n.py --check el-rGR

# 4. Test get command
python l10n.py --get el-rGR show

# 5. Test get-plurals command
python l10n.py --get-plurals el-rGR notification_incognito_running_title
```

### Validation Tests (Should Fail with Clear Errors)

These tests verify that validation is working correctly by testing invalid inputs:

```powershell
# Test 1: Invalid entity &pos; (should FAIL)
python l10n.py --set el-rGR show "Test &pos; here"
# Expected: ERROR - Invalid entity '&pos;' detected!

# Test 2: Invalid entity &apos; (should FAIL)
python l10n.py --raw --set el-rGR show "Don&apos;t use this"
# Expected: ERROR - Entity '&apos;' detected! Use \' instead.

# Test 3: Invalid entity &quot; (should FAIL)  
python l10n.py --raw --set el-rGR show 'Text &quot;quoted&quot;'
# Expected: ERROR - Entity '&quot;' detected! Use \" instead.

# Test 4: Unquoted XML attribute (should FAIL)
python l10n.py --raw --set el-rGR show '<xliff:g id=bad>%s</xliff:g>'
# Expected: ERROR - Attribute value not quoted: id=bad

# Test 5: Mismatched XML tags (should FAIL)
python l10n.py --raw --set el-rGR show '<xliff:g>text</b>'
# Expected: ERROR - Mismatched tags

# Test 7: Unclosed XML tag (should FAIL)
python l10n.py --raw --set el-rGR show '<xliff:g id="test">text'
# Expected: ERROR - Unclosed tag(s)

# Test 8: Invalid custom entity (should FAIL)
python l10n.py --raw --set el-rGR show 'Text &custom; entity'
# Expected: ERROR - Invalid entity reference(s): &custom;

# Test 9: Unterminated entity (should FAIL)
python l10n.py --raw --set el-rGR show 'Text &amp more'
# Expected: ERROR - Unterminated entity reference: '&amp'
```

### Valid Input Tests (Should Succeed)

These tests verify that valid inputs are accepted:

```powershell
# Test 1: Simple text (should SUCCEED)
python l10n.py --set el-rGR show "Εμφάνιση"
# Expected: [OK] Successfully updated

# Test 2: Text with placeholder (should SUCCEED)
python l10n.py --set el-rGR dialog_title '%1$s settings'
# Expected: [OK] Successfully updated

# Test 3: Valid XML with --raw and here-string (should SUCCEED)
$value = @'
Match <xliff:g id="current_match" example="1">%1$d</xliff:g> of <xliff:g id="match_count" example="10">%2$d</xliff:g>
'@
python l10n.py --raw --set el-rGR match_x_of_n $value
# Expected: [OK] Successfully updated

# Test 4: Valid entities (should SUCCEED)
python l10n.py --raw --set el-rGR test 'Text &amp; &lt; &gt; symbols'
# Expected: [OK] Successfully updated

# Test 5: Numeric entities (should SUCCEED)
python l10n.py --raw --set el-rGR test 'Text &#169; &#x1F4A1; entities'
# Expected: [OK] Successfully updated

# Test 6: Batch update (should SUCCEED)
python l10n.py --set el-rGR enable 'Enable' disable 'Disable' show 'Show'
# Expected: [OK] for all three strings

# Test 7: Plural update (should SUCCEED)
python l10n.py --set-plurals el-rGR notification_incognito_running_title other '%1$d tabs'
# Expected: [OK] Updated quantity 'other'
```

### XML Compilation Test

After any changes, verify that Android XML resources compile correctly:

```powershell
# Verify all translation files have valid XML structure (PowerShell multi-line)
python -c @"
import xml.etree.ElementTree as ET
import pathlib
res_dir = pathlib.Path('app/src/main/res')
files = list(res_dir.glob('values-*/strings.xml'))
print(f'Checking {len(files)} files...')
[ET.parse(f) for f in files]
print('All XML files valid!')
"@
```

### Automated Test Script

For convenience, create and use a test script:

```powershell
# Create test_l10n.ps1 to run all regression tests
# Then run it with:
.\test_l10n.ps1
```

### Test Coverage

The regression tests cover:

- ✅ **Command Interface**: All commands execute without errors
- ✅ **Validation Logic**: Invalid inputs are rejected with clear errors  
- ✅ **Valid Inputs**: Legitimate strings are accepted
- ✅ **Android Entities**: `&apos;`, `&quot;`, `&pos;` detection
- ✅ **XML Structure**: Tag matching, attribute quoting
- ✅ **Entity References**: Valid vs invalid entities
- ✅ **Batch Operations**: Multiple strings at once
- ✅ **Plural Support**: Plural quantity handling
- ✅ **XML Compilation**: Final XML is valid and parseable

### When to Run Tests

Run regression tests:

1. **After modifying `l10n.py`** - Ensure changes didn't break existing functionality
2. **Before committing** - Verify all validation logic works
3. **When reporting issues** - Confirm the issue with test cases
4. **Periodically** - Ensure tool remains functional as project evolves

### Adding New Tests

When adding new validation rules to `l10n.py`, add corresponding tests to this section showing:
1. What the rule validates
2. Example of invalid input that should be rejected
3. Example of valid input that should be accepted
4. Expected error message for failures

## Publishing to Google Play: `publish_google_play.py`

The `fastlane/publish_google_play.py` script automates uploading translated metadata to Google Play Store.

### Prerequisites

1. **Python packages**:
   ```bash
   pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
   ```

2. **Google Play Service Account JSON key** - See `GET_JSON_KEY.md` for setup instructions.

### Usage

```bash
# Upload metadata (titles, descriptions)
python fastlane\publish_google_play.py "path\to\service-account.json"
```

### What It Does

1. **Authenticates** with Google Play API using service account
2. **Creates an edit session** for your app
3. **For each language** in `fastlane/metadata/android/`:
   - Reads `title.txt` (app name, max 30 chars)
   - Reads `short_description.txt` (tagline, max 80 chars)
   - Reads `full_description.txt` (feature list, max 4000 chars)
   - Uploads or updates the listing for that language
4. **Commits changes** to publish (or reports issues)

---

## Managing Changelogs: `changelogs.py`

The `fastlane/changelogs.py` script compiles changelogs from all languages into Google Play Console's bulk upload format.

### Location

```
fastlane/
├── publish_google_play.py  (upload metadata)
└── changelogs.py           (compile changelogs)
```

### Prerequisites

Changelog files must exist at: `fastlane/metadata/android/{language}/changelogs/{version}.txt`

**Important:**
- Changelog max length: 500 characters per language
- Changelogs are uploaded manually via Google Play Console bulk editor

### Usage

```bash
# Compile changelog template for version 239
python fastlane\changelogs.py 239
```

### What It Does

1. **Reads all changelog files** from `fastlane/metadata/android/{lang}/changelogs/239.txt`
2. **Compiles into Google Play format**:
   ```xml
   <en-US>
   🌍 Now available in 25 languages!
   ...
   </en-US>
   
   <cs-CZ>
   🌍 Nyní dostupné ve 25 jazycích!
   ...
   </cs-CZ>
   ```
3. **Copies to clipboard** using PowerShell for proper UTF-8 encoding
4. **Shows statistics**: Found changelogs and any missing files

### Workflow

1. **Create changelog files** for each language in `fastlane/metadata/android/{lang}/changelogs/{version}.txt`
2. **Run changelogs.py**: `python fastlane\changelogs.py 239`
3. **Paste into Google Play Console**: Release management → Edit release → Release notes → Use bulk editor
4. **Publish release**: Changelogs will be shown to users on update

### Configuration

Edit the script to change:
- **Package name**: `PACKAGE_NAME = 'net.slions.fulguris.full.playstore'`
- **Languages list**: Modify the `languages` array

### Notes

- Languages must be added in Play Console first (Store presence → Main store listing)
- JSON key file must never be committed to Git (already excluded in `.gitignore`)
- Script validates string lengths and handles errors gracefully
