# EllesmereUI — Localization Script (Handoff Notes)

*Built/tested against EllesmereUI **7.9.5**.*

This adds a complete, **non-invasive localization layer** to EllesmereUI: every
user-facing string becomes a translatable key, so the addon can ship in **any
language**. It's a localization script (not a single translation) — adding a new
language is just filling in one value table per locale.

As the **first locale**, I've bundled a full **Korean (koKR)** translation (I'm
Korean, so I filled that one in as the working example). The system itself is
locale-agnostic; English (enUS) is the untouched master and other languages drop in
the same way.

Everything is driven from one data file and applied by a script that **locates your
WoW `Interface\AddOns\EllesmereUI` folder and patches it** (wraps the strings and
installs the locale files), so it's easy to maintain and to merge upstream.

---

## 1. How the localization works

**Three pieces:**

1. **`EllesmereUI/Locale/LocalCore.lua`** (loads first)
   Defines the global translation table:
   ```lua
   EUI_L = EUI_L or setmetatable({}, { __index = function(_, k) return k end })
   ```
   Any unknown key returns *the key itself* (the English text), so a missing
   translation can never break a `SetText`.

2. **`EllesmereUI/Locale/<locale>.lua`** (one file per locale)
   Plain key → value tables:
   ```lua
   if GetLocale() ~= "koKR" then return end
   local L = EUI_L
   L["Cooldown Manager"] = "쿨다운 관리자"
   ```
   - `enUS.lua` is the **master**: it lists *every* key with `L["x"] = "x"` and is
     the reference for translators.
   - Locales are auto-loaded only when `GetLocale()` matches.

3. **The source code** is wrapped so displayed strings go through `EUI_L`:
   ```lua
   obj:SetText(EUI_L["Cooldown Manager"])
   ```
   **Only user-visible text is wrapped.** Identifiers, table keys, frame names,
   comparisons (`==`/`~=`), API arguments (`SetFrameStrata`, etc.), texture/atlas
   paths, font paths and SharedMedia asset names are deliberately left in English
   so logic never changes. Where a value is both shown and used as a key, the
   wrap is applied at the *display* site only (e.g. `EUI_L[bar.name]`), keeping the
   stored identifier English.

**Context-sensitive keys (same English word, different translation per module).**
A few strings need a *different* translation depending on which module shows them —
e.g. **"Armor"** is the item category in the bags (Korean 방어구) but the defense
stat on the character sheet (Korean 방어도), while English stays one word. These are
registered in the locale (not via `L["Armor"]`) as:
```lua
EUI_L_CTX["Armor"] = { ["*"] = "방어도", ["EllesmereUIBags"] = "방어구" }
```
`LocalCore`'s `__index` resolves them by checking which addon is calling
(`debugstack`) and returns the per-module value, falling back to `["*"]`. **Your
source is never changed for this** — the bag and character-sheet code already call
`EUI_L["Armor"]`; only the locale layer differs, and English is unaffected. Only
these few keys pay the stack lookup; all other keys keep the fast direct-table path.
Other locales that don't need the split just omit the `EUI_L_CTX` entry.

**The `.toc` is patched** to load `Locale/LocalCore.lua` and the locale files
right after the core file.

---

## 2. Applying / regenerating

The whole thing is generated from a single source of truth and applied by a script:

- **`_EUI_Localize/Locale-Data.ps1`** — single source of truth (all keys + every
  locale's values + the list of source lines to wrap).
- **`_EUI_Localize/Build-ApplyScript.ps1`** — builds `Apply-EllesmereUI-Locale.ps1`
  and the locale files from the data.
- **`_EUI_Localize/Apply-EllesmereUI-Locale.ps1`** — the installer. It first
  **locates your WoW install and its `Interface\AddOns\EllesmereUI` folder**
  (registry → running process → common install paths; or `-AddonPath` to override),
  then patches that copy:
  1. creates `Locale/LocalCore.lua` + one file per locale,
  2. inserts those into the `.toc`,
  3. wraps the target strings in the source (line-exact, whitespace-trimmed match,
     idempotent — safe to run repeatedly; any line that no longer matches is
     reported as `NOT FOUND` instead of being changed).

To merge upstream: keep the `EUI_L` wraps (they are harmless in any locale) and
the `Locale/` files; translators only ever edit the per-locale `L["key"] = "..."`
values.

### Command-line usage
Run from PowerShell (auto-detect is described above; `-AddonPath` overrides it):
```powershell
# apply (asks before writing)
.\Apply-EllesmereUI-Locale.ps1

# apply without the prompt
.\Apply-EllesmereUI-Locale.ps1 -Force

# point at a specific install if auto-detect misses it
.\Apply-EllesmereUI-Locale.ps1 -AddonPath "D:\World of Warcraft\_retail_\Interface\AddOns\EllesmereUI"

# TEST / coverage mode
.\Apply-EllesmereUI-Locale.ps1 -Test -Force
```

**`-Test` (coverage check):** prepends a bright magenta **`[L]`** tag (colored text,
no icon) to *every* localized value. In game, any text **with** the `[L]` tag is
covered by this localization; any UI text **without** it is still a raw/un-wrapped
string (or comes straight from the WoW client). Use it to spot what is or isn't
translated, then re-run **without** `-Test` to ship the clean build.

> Tip: the `[L]` marker also reveals the font issue from §3 cleanly — a marked value
> that shows as `[L]□□□` is translated but can't be drawn (font), whereas an
> unmarked English string was simply never wrapped.

> ⚠️ **`-Test` and SavedVariables — important caveat.** EllesmereUI persists *resolved
> display strings* (not keys) into its SavedVariables — e.g. CDM bar names, bag
> category names, stat-group names, profile/spec labels. So whatever `EUI_L[...]`
> returns at save time is what gets written to disk. If the addon saves any of these
> while a **`-Test`** build is active, the `|cffff3cff[L]|r ` prefix is baked into the
> saved value and **keeps showing even after you re-apply a clean (non-Test) build** —
> because the contamination lives in the *saved data*, not in the addon files (locale
> files / source will scan clean). This is inherent to EUI storing translated display
> names rather than stable keys; the localization layer can't prevent it.
> **Fix:** use `-Test` only for quick coverage checks, ship/keep the clean build, and
> if `[L]` lingers in saved names, with the **game fully closed** either delete the
> relevant `SavedVariables/EllesmereUI*.lua` or strip the `|cffff3cff[L]|r ` prefix
> from them (editing while the client is running gets overwritten on logout).

---

## 3. ⚠️ REQUIRED for multi-language: one-line font fix (we did NOT touch your code)

This package ships Korean translations, and the localization layer is ready for any
locale. But a few windows draw text with the bundled **`Expressway.ttf`**, which has
**no CJK/Cyrillic glyphs**, so Korean would render as **□ boxes (tofu)** there.

### Windows affected — kept in English ON PURPOSE
To avoid boxes, these windows are **left untranslated (English values)** — we did
NOT give them Korean, precisely because they would otherwise show boxes:

| Where the boxes are | What it is |
|---------------------|------------|
| **First-run setup window** | the addon-pick screen shown on first login |
| **Spec-assignment grid** | popup from the **sword icon** on Resource & Cast Bars → Health/Power Bar → "Enable for these specs" |
| **Color picker** | the "cancel" text |
| **Damage Meters** | the unlock-mode overlay label |
| **"Global Font" notice** | text on the Fonts & Colors page |

> **Side effect:** the first-run window shares its module-name keys with the **left
> settings sidebar**, so those sidebar names (Action Bars, Bags, Chat, Cooldown
> Manager, …) also show in **English**. This is intentional and tied to the same font
> limitation — fixing the font (below) lets all of them be translated.

**Where this Latin-only font (`Expressway.ttf` / `EllesmereUI.EXPRESSWAY`) is used**
(so you know the full scope — a full audit). These all box on CJK clients and are why
we kept the above untranslated; the one-line fix below resolves every one of them:
- the small parenthetical **suffix labels** next to sliders — `(seconds)`, `(min)`,
  `(minutes)`, `(Percent)`, `(Ex: 1,5,8)`, `(Applies on Window Close)` — in
  Nameplates / Damage Meters / Party Mode / AuraBuff Reminders / Cooldown Manager / Widgets
- the **"Reset"** buttons on the Chat and Minimap option panels
- the **"Low Durability"** on-screen warning (QoL)
- the color-picker hex/cancel text, and several edit-box placeholders
- the first-run window, the spec-assignment grid, and the Damage Meters overlay (above)

> We left these as English values (untranslated) so they read cleanly while the font
> is Latin-only; once the font line is fixed, their koKR values can be filled in.

**We did NOT modify your source's font logic** (you asked us to keep the original
intact). After the one-line font fix below, these windows can use a CJK-capable font
and the English placeholders can be filled in with Korean.

**The mechanism already exists in your code — koKR is already configured.** In
`EllesmereUI.lua` (~line 258) the per-locale font fallback already includes koKR:
```lua
local LOCALE_FONT_FALLBACK
do
    local _locale = GetLocale()
    if _locale == "zhCN" or _locale == "zhTW" then
        LOCALE_FONT_FALLBACK = "Fonts\\ARKai_T.ttf"
    elseif _locale == "koKR" then
        LOCALE_FONT_FALLBACK = "Fonts\\2002.TTF"     -- Korean-capable, already here
    elseif _locale == "ruRU" then
        LOCALE_FONT_FALLBACK = "Fonts\\FRIZQT___CYR.TTF"
    end
end
EllesmereUI.EXPRESSWAY = LOCALE_FONT_FALLBACK or EXPRESSWAY
```
So `EllesmereUI.EXPRESSWAY` is **supposed** to resolve to `2002.TTF` on koKR. But a
later line silently overrides it back to the Latin-only font:

### Root cause — one line clobbers the locale fallback
`EllesmereUI.lua` sets it correctly at **line 464**:
```lua
EllesmereUI.EXPRESSWAY = LOCALE_FONT_FALLBACK or EXPRESSWAY   -- = 2002.TTF on koKR  ✔
```
…then **re-assigns it at line ~2643**, dropping the fallback:
```lua
EllesmereUI.EXPRESSWAY = EXPRESSWAY      -- = Expressway.ttf (Latin only)  ✘  clobbers the fallback
```
Because this runs after 464, the locale fallback is lost. That value also propagates
to `EllesmereUI._font` (`EllesmereUI_Widgets.lua:1548 EllesmereUI._font = EXPRESSWAY`),
so all the windows listed above fall back to a Latin-only font → boxes.

### The fix — one line
Make line ~2643 respect the fallback, exactly like line 464:
```lua
EllesmereUI.EXPRESSWAY = LOCALE_FONT_FALLBACK or EXPRESSWAY
```
That single change restores `2002.TTF` on koKR (and the zh*/ru fallbacks), cascades
into `EllesmereUI._font`, and **every previously-boxed window renders Korean** —
with **no effect on enUS** (where `LOCALE_FONT_FALLBACK` is nil → `EXPRESSWAY`).

### Per-locale default fonts (already mapped by `LOCALE_FONT_FALLBACK`)
The fix simply makes this existing table take effect:

| Locale | WoW default font used | Needs fallback? |
|--------|-----------------------|-----------------|
| **koKR** | `Fonts\2002.TTF` | yes (boxes without it) |
| **ruRU** | `Fonts\FRIZQT___CYR.TTF` | yes |
| zhCN / zhTW | `Fonts\ARKai_T.ttf` | yes |
| enUS, deDE, frFR, esES, esMX, ptBR, itIT | — (Expressway has Latin glyphs) | no |

> **Why not do this from our locale files instead?** We tried setting
> `EllesmereUI._font` from `LocalCore.lua`, but `EllesmereUI_Widgets.lua:1548`
> re-assigns `_font` after locale files load (and the color picker caches its font at
> load), so this single source-line at ~2643 is the only complete, reliable place.
>
> **I can apply this one-liner on my end as a temporary measure** to ship the build,
> but it'd be better fixed in the original source: one token there fixes **every**
> CJK/Cyrillic locale at once and spares downstream translators from re-patching it
> each update. Would you consider taking it upstream?

### Not a font issue (intentional, leave as English)
- **Font names** (Expressway, Friz Quadrata, Poppins, …) and **texture / SharedMedia
  asset names** (Glass, Gradient, Blizzard Dialog, …) are kept in English on
  purpose — they are asset identifiers, not UI copy.

---

## 4. Korean-client tooltip parsing (bonus fix included)

The Upgrade Calculator parsed gear upgrade tracks from the **English** tooltip
string `"Upgrade Level: <Track> n/m"`. On a Korean client the tooltip reads
`"레벨 강화: <한글트랙> n/m"`, so parsing failed and crest totals showed `0`.
A locale-aware fallback was added in `Calc:ScanItemLink` (maps the Korean track
names back to the English track keys) so crest math works on koKR clients. This is
a real bug fix and is safe for all locales (English path is tried first).

---

## TL;DR for the author
- This is a **localization script** for EllesmereUI, not just one translation —
  it makes every UI string translatable; **Korean (koKR) is included as the first
  example locale** (I'm Korean), and any other language drops in the same way.
- The installer **finds your WoW `Interface\AddOns\EllesmereUI` folder and patches
  it**. It's opt-in per locale and cannot break English builds (enUS = untouched master).
- Keep the `EUI_L["..."]` wraps and the `Locale/` files; translators edit only the
  per-locale value tables.
- **Apply the one-line font fix at `EllesmereUI.lua` ~2643**
  (`EllesmereUI.EXPRESSWAY = LOCALE_FONT_FALLBACK or EXPRESSWAY`) so the listed
  windows use a CJK-capable font. The Korean translations are **already in place** —
  they render instead of boxes the moment that font line is fixed. enUS is unaffected.
