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
155 changes: 155 additions & 0 deletions tools/i18n/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# 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_override.ini
[env:esp32dev_zh_CN]
extends = env:esp32dev
custom_usermods = https://github.com/foxlesbiao/WLED-translations
build_flags = ${env:esp32dev.build_flags} -D WLED_LOCALE=zh_CN
extra_scripts = pre:tools/i18n/build.py
```

PlatformIO automatically clones the translation repo to `.pio/libdeps/`.

### Build Steps

1. PlatformIO clones `WLED-translations` to `.pio/libdeps/<env>/WLED-translations/`
2. `build.py` auto-detects translations in `.pio/libdeps/`
3. Applies L1/L2 translations via regex replacement
4. Generates `i18n_effects.h` / `i18n_palettes.h` for L3/L4 (PROGMEM replacement)
5. 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/
└── en_template.json # English template for translators
```

### 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%** |
142 changes: 142 additions & 0 deletions tools/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# WLED i18n Toolchain

Build-time internationalization for WLED Web UI. Translates HTML/JS strings at compile time with **zero runtime overhead** and **zero flash overhead** (replaces, not adds).

## How It Works

```
English HTM files (wled00/data/)
extract.py → en_template.json
Translator creates locale repo (WLED-translations)
build.py → Translated HTM files
npm run build → html_*.h / js_*.h (C headers)
pio run → Firmware with translated UI
```

## Quick Start (User)

Add to `platformio_override.ini`:

```ini
[env:esp32dev_zh_CN]
extends = env:esp32dev
custom_usermods = https://github.com/foxlesbiao/WLED-translations
build_flags = ${env:esp32dev.build_flags} -D WLED_LOCALE=zh_CN
extra_scripts = pre:tools/i18n/build.py
```

Then: `pio run -e esp32dev_zh_CN`

PlatformIO automatically clones the translations repo to `.pio/libdeps/`. The build script finds translations there automatically.

## Quick Start (Translator)

Translations live in a separate repo: [WLED-translations](https://github.com/foxlesbiao/WLED-translations)

```
WLED-translations/
├── library.json # PlatformIO dependency manifest
├── zh_CN/
│ ├── 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
├── de_DE/
│ └── ...
└── en_template/ # English template (generated by extract.py)
```

### Adding a new language

```bash
# 1. Clone translations repo
git clone https://github.com/foxlesbiao/WLED-translations
cd WLED-translations

# 2. Generate English template (from WLED source)
python3 /path/to/WLED/tools/i18n/extract.py --stats
cp /path/to/WLED/tools/i18n/locales/en_template.json en_template/

# 3. Create your locale
mkdir de_DE
cp en_template/*.json de_DE/

# 4. Fill in "translation" fields in each JSON file
# 5. Commit and push
```

## Quick Start (Developer)

### Extract strings (generate template)

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

### Build translated firmware

```bash
# Build translated HTM files
python3 tools/i18n/build.py --locale zh_CN \
--translations-dir /path/to/WLED-translations/zh_CN \
--output-dir build/i18n/zh_CN

# Validate translations
python3 tools/i18n/build.py --locale zh_CN --validate

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

# Build firmware
pio run -e esp32dev
```

## Translation Search Order

`build.py` searches for translations in this order:

1. `--translations-dir` (explicit path)
2. `.pio/libdeps/*/WLED-translations/<locale>/` (PlatformIO out-of-tree)
3. `tools/i18n/locales/<locale>.json` (local fallback)

## Translation JSON Format

```json
{
"index.htm": {
"html:body > div#btns > a:nth-of-type(1):text": {
"en": "Power",
"translation": "电源",
"context": "index.htm: (html_text)"
},
"js:index.htm:45:a1b2c3d4": {
"en": "Loading...",
"translation": "加载中...",
"context": "index.htm:45 (js_innerHTML)"
}
}
}
```

## Coverage

| Layer | Content | Method | Count |
|-------|---------|--------|-------|
| 1. Static HTML | Labels, buttons, placeholders | DOM text matching | 429 |
| 2. JS strings | `alert()`, `innerHTML`, `innerText` | Script block regex | 45 |
| 3. Effect names | FX names in `colors.cpp` | PROGMEM replacement | 216 |
| 4. Palette names | Palette names in `colors.cpp` | PROGMEM replacement | 72 |

## Limitations

1. **No runtime language switching** — language is fixed at build time
2. **JS template literals with `${...}`** — partial strings can't be safely replaced
3. **C++ server-side strings** — ~12 strings in `xml.cpp` need `#ifdef WLED_LOCALE_*`
4. **External tools** (pixelforge, pixelmagic) — always English, downloaded on-the-fly
Loading
Loading