Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
733fe56
中文翻译:完成全部22个Web UI页面
foxlesbiao Jun 5, 2026
f03fc56
修复 CodeRabbit 审查发现的遗漏翻译
foxlesbiao Jun 5, 2026
33a41c5
Merge branch 'main' into chinese-translation
softhack007 Jun 5, 2026
b070009
Merge branch 'main' into chinese-translation
softhack007 Jun 5, 2026
d31805a
Add i18n toolchain for Web UI localization
foxlesbiao Jun 6, 2026
d09bc9e
Complete zh_CN translations for all JS strings flagged by softhack007
foxlesbiao Jun 6, 2026
65a70c6
Fix 4 critical bugs in build.py from coderabbitai review
foxlesbiao Jun 6, 2026
d88b975
Remove 'value' from translatable attributes
foxlesbiao Jun 6, 2026
dcb442f
Fix BUILD_FLAGS parsing to handle no-space form
foxlesbiao Jun 6, 2026
4c72192
Add Layer 3/4 translations: effects (216) and palettes (72)
foxlesbiao Jun 6, 2026
f4055d9
docs: add i18n architecture documentation
foxlesbiao Jun 6, 2026
e7ae442
Merge branch 'i18n-toolchain' into chinese-translation
foxlesbiao Jun 6, 2026
724ae6f
refactor: keep i18n toolchain only, move translations out-of-tree
foxlesbiao Jun 7, 2026
43ac198
refactor: out-of-tree translation support via PlatformIO custom_usermods
foxlesbiao Jun 9, 2026
15f0f9e
revert: remove direct HTM file changes, keep i18n tools only
foxlesbiao Jun 10, 2026
303ec84
feat: add diff.py for version comparison, update README with limitations
foxlesbiao Jun 10, 2026
73a1c2f
feat: add self-evolving translation scripts
foxlesbiao Jun 13, 2026
f9f8222
Merge branch 'main' into chinese-translation
softhack007 Jun 23, 2026
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) │
Comment thread
coderabbitai[bot] marked this conversation as resolved.
│ ├── 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%** |
187 changes: 187 additions & 0 deletions tools/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# WLED i18n Toolchain

Build-time internationalization for WLED Web UI. Translates HTML/JS strings at compile time — replaces English text, does not add to it.

## 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 (716 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 -r /path/to/WLED/tools/i18n/locales/* 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
```

### Version updates (diff)

When WLED releases a new version, compare templates to find changes:

```bash
# Generate old and new templates
python3 tools/i18n/extract.py --stats # on old version
cp tools/i18n/locales/en_template.json en_template_old.json

python3 tools/i18n/extract.py --stats # on new version
cp tools/i18n/locales/en_template.json en_template_new.json

# Compare
python3 tools/i18n/diff.py --old en_template_old.json --new en_template_new.json
```

Output shows added/removed/modified strings. Translators update only changed entries.

## 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)

## Coverage

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

## Known Limitations

### Cannot translate (technical)

- **Dynamic runtime text** — OTA update errors, Info page content, usermod settings, Pin Info page
- **External tools** — PixelForge add-ons (always English, downloaded on-the-fly)
- **JavaScript template literals** — strings with `${...}` interpolation
- **C++ server-side strings** — ~12 strings in `xml.cpp` need `#ifdef WLED_LOCALE_*`

### Language-specific issues (acknowledged)

- Word order differences (e.g., "X of Y" patterns)
- Number formats (decimal point vs comma)
- Grammar rules (singular/plural, countable/uncountable)
- Date formats

These are known limitations. The tool handles short labels and UI fragments, not full sentences.

## Layer 3/4: Effect & Palette Names

Effect names (216) and palette names (72) are translated via C++ PROGMEM replacement:

```c
// locale_effects.h (auto-generated)
#pragma once
#ifdef WLED_LOCALE
#undef _data_FX_MODE_STATIC
static const char _data_FX_MODE_STATIC[] PROGMEM = "常亮";
// ...
#endif
```

The `.h` files are generated in the translation repo. Users copy them to their local build.

## Architecture

```
WLED (core repo)
└── tools/i18n/
├── extract.py # Extract strings from HTML/JS
├── build.py # Apply translations (pre-build script)
├── diff.py # Compare template versions
└── README.md

WLED-translations (community repo)
├── zh_CN/
│ ├── static.json # Layer 1: HTML text
│ ├── js.json # Layer 2: JS strings
│ ├── effects.json # Layer 3: effect names
│ └── palettes.json # Layer 4: palette names
└── en_template/ # English template
```
Loading
Loading