Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions tools/i18n/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# WLED i18n Architecture

## Overview

Two-repository architecture separating **build toolchain** (core repo) from **translation files** (community repo).

```
┌─────────────────────────────────────────────────────────────┐
│ Core Repo (WLED/tools/i18n/) │
│ ├── extract.py # Extract translatable strings from HTML │
│ ├── build.py # Apply translations at build time │
│ └── locales/ # Locale configuration │
└─────────────────────────────────────────────────────────────┘
↓ calls
┌─────────────────────────────────────────────────────────────┐
│ Translation Repo (WLED-translations/<locale>/) │
│ ├── static.json # Layer 1: Static HTML (429 entries) │
│ ├── js.json # Layer 2: JS strings (45 entries) │
│ ├── effects.json # Layer 3: Effect names (216 entries) │
│ ├── palettes.json # Layer 4: Palette names (72 entries) │
│ └── metadata.json # Version, coverage, maintainer │
└─────────────────────────────────────────────────────────────┘
```

---

## Four-Layer Translation Architecture

| Layer | Content | File | Implementation | Coverage |
|-------|---------|------|----------------|----------|
| **L1** | Static HTML | `static.json` | Regex replacement in HTML text | 429 strings |
| **L2** | JS strings | `js.json` | Replace JS string literals | 45 strings |
| **L3** | Effect names | `effects.json` | C++ PROGMEM `#undef` + redefine | 216/216 (100%) |
| **L4** | Palette names | `palettes.json` | C++ PROGMEM array replacement | 72/72 (100%) |

---

## Build Flow

### PlatformIO Configuration

```ini
# platformio.ini
[env:esp32dev_zh_CN]
extends = env:esp32dev
build_flags = ${env:esp32dev.build_flags} -D WLED_LOCALE=zh_CN
extra_scripts =
${env:esp32dev.extra_scripts}
pre:tools/i18n/build.py
```

### Build Steps

1. `build.py` reads `wled00/data/*.htm` (English source)
2. Applies L1/L2 translations via regex replacement
3. Generates `i18n_effects.h` / `i18n_palettes.h` for L3/L4 (PROGMEM replacement)
4. Output to `build/i18n/<locale>/`

---

## How Dynamic Content Works

The key insight: **PROGMEM replacement happens at compile time**, so JSON endpoints return translated strings automatically.

```
Browser ESP32 Firmware
│ │
├─ GET /json/palettes ─────────→│ PROGMEM array was replaced at compile time
│ ← {"0":"默认","1":"* 随机循环",...} ↓
│ │ palettes.json → i18n_palettes.h
├─ GET /json/effects ─────────→│ #undef _data_FX_MODE_STATIC
│ ← {"0":"常亮","1":"闪烁",...}│ #define _data_FX_MODE_STATIC "常亮"
```

No firmware code changes needed. The C++ PROGMEM strings are the single source of truth.

---

## Grammar and Word Order

WLED UI uses **short labels**, not full sentences:

| Pattern | Example | i18n Impact |
|---------|---------|-------------|
| Single word | "Brightness", "Speed" | ✅ No issue |
| Label + value | "255 segments" | ✅ Works ("255 个段") |
| Full sentences | Almost none | ✅ N/A |
| Plural forms | Not used | ✅ N/A |
| Date formats | Not used in UI | ✅ N/A |

The architecture **intentionally avoids** complex i18n patterns (ICU MessageFormat, plural rules) because WLED's UI doesn't need them.

---

## What's NOT Translated (By Design)

| Content | Reason |
|---------|--------|
| User-defined preset names | Belongs to user |
| Usermod settings pages | Dynamic HTML from firmware, varies by hardware |
| System info (IP, memory) | Universal data |
| Effect slider tooltips | Generated from mode data arrays |

---

## Repository Structure

### Core repo (WLED)

```
tools/i18n/
├── extract.py # String extraction tool
├── build.py # Build-time translation applicator
├── ARCHITECTURE.md # This file
├── README.md # Usage documentation
└── locales/ # Locale configs
```

### Translation repo (WLED-translations)

```
<locale>/
├── static.json # Layer 1: Static HTML text
├── js.json # Layer 2: JavaScript strings
├── effects.json # Layer 3: Effect names (PROGMEM)
├── palettes.json # Layer 4: Palette names (PROGMEM)
└── metadata.json # {"version":"1.0","coverage":"100%","maintainer":"..."}
en_template/ # English template for translators
```

---

## Adding a New Language

1. Fork `WLED-translations`
2. Copy `en_template/` to `<locale>/`
3. Translate JSON files
4. Submit PR to translation repo

No changes to WLED core needed.

---

## Coverage Summary (zh_CN)

| Layer | Content | Count | Status |
|-------|---------|-------|--------|
| L1 | Static HTML | 429 | ✅ Complete |
| L2 | JS strings | 45 | ✅ Complete |
| L3 | Effect names | 216/216 | ✅ 100% |
| L4 | Palette names | 72/72 | ✅ 100% |
| **Total** | | **762** | **100%** |
119 changes: 119 additions & 0 deletions tools/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# WLED i18n Toolchain

Build-time internationalization for WLED Web UI. Translates HTML/JS strings at compile time with **zero runtime overhead**.

## Architecture

```
WLED (core repo) WLED-translations (community repo)
tools/i18n/ <locale>/
├── extract.py ──────────────────→ static.json (Layer 1: HTML)
├── build.py ←─ applies ─────── js.json (Layer 2: JS)
├── ARCHITECTURE.md effects.json (Layer 3: PROGMEM)
└── README.md palettes.json(Layer 4: PROGMEM)
```

**Key principle:** Translations are maintained **out-of-tree** by community contributors, similar to usermods. Users build translated firmware locally.

## Quick Start

### 1. Clone both repos

```bash
git clone https://github.com/wled/WLED.git
git clone https://github.com/wled/WLED-translations.git
```

### 2. Extract strings (optional, for translators)

```bash
cd WLED
python3 tools/i18n/extract.py --stats
# Output: tools/i18n/locales/_template.json
```

### 3. Validate translations

```bash
# Check coverage for a locale
python3 tools/i18n/extract.py --validate zh_CN

# Output:
# Validating locale: zh_CN
# ========================================
# Coverage: 429/429 (100.0%)
# PASSED: All strings translated
```

### 4. Build translated firmware

```bash
cd WLED

# Apply translations (Layer 1 + 2: HTML/JS)
python3 tools/i18n/build.py --locale zh_CN \
--source-dir wled00/data \
--translation-dir ../WLED-translations/zh_CN \
--output-dir build/i18n/zh_CN

# Build web UI headers
npm ci && npm run build

# Build firmware
pio run -e esp32dev
```

### 5. PlatformIO integration (automatic)

Add to `platformio.ini`:

```ini
[env:esp32dev_zh_CN]
extends = env:esp32dev
build_flags = ${env:esp32dev.build_flags} -D WLED_LOCALE=zh_CN
extra_scripts = pre:tools/i18n/build.py
```

Then just: `pio run -e esp32dev_zh_CN`

## Four-Layer Translation System

| Layer | Content | Source | Method |
|-------|---------|--------|--------|
| **L1** | Static HTML | `static.json` | Regex replacement |
| **L2** | JS strings | `js.json` | Script block regex |
| **L3** | Effect names | `effects.json` | PROGMEM `#undef` + redefine |
| **L4** | Palette names | `palettes.json` | PROGMEM array replacement |

## For Translators

1. Fork [WLED-translations](https://github.com/wled/WLED-translations)
2. Copy `en_template/` to `<locale>/`
3. Edit JSON files — fill in `"translation"` fields
4. Validate: `python3 tools/i18n/extract.py --validate <locale>`
5. Submit PR to WLED-translations repo

## For Maintainers

### Adding this toolchain to WLED core

This PR adds only `tools/i18n/` — no changes to existing build pipeline. The tool is a pre-build step that runs before `npm run build`.

### CI/CD integration

```yaml
# .github/workflows/i18n-validate.yml
- name: Validate translations
run: |
git clone https://github.com/wled/WLED-translations.git
for locale in WLED-translations/*/; do
python3 tools/i18n/extract.py --validate $(basename $locale)
done
```

## Limitations

1. **No runtime language switching** — language is fixed at build time
2. **External tools** (pixelforge, pixelmagic) — always English, downloaded on-the-fly
3. **C++ server-side strings** — ~12 strings in `xml.cpp` need separate handling
4. **User presets** — user-defined names are not translated (by design)
Loading