Localizations
Muxy includes English. An enabled extension can add one or more app languages by declaring muxy.localizations. Users can discover language providers from Settings → Interface → Language → Browse Language Extensions…, which opens the Extension Store's localization category. After installation and enablement, the user chooses a provider in Settings → Interface → Language. If several enabled extensions provide the same language, each appears separately with its extension name.
Translations do not belong in package.json. The manifest contains only provider metadata and a path:
{
"name": "muxy-german",
"version": "1.0.0",
"scripts": {
"build": "vite build && node scripts/copy-manifest.mjs && cp -R localization dist/localization"
},
"muxy": {
"localizations": [
{
"id": "de",
"language": "de",
"title": "Deutsch",
"bundle": "localization/German.bundle"
}
]
}
}
| Field | Meaning |
|---|---|
id | Provider ID unique inside the extension. Letters, digits, ., _, and - are accepted. |
language | BCP 47 language identifier such as de, fr, or pt-BR. It must exactly match the .lproj directory name. |
title | Name shown in Settings, preferably written in the language itself. |
bundle | Relative path inside the installed dist/ output to a resource-only .bundle directory. |
Bundle layout
The .bundle is an Apple resource directory, not executable code and not a compiled framework. It must be copied into dist/, but it does not need to be linked into the extension's JavaScript build.
localization/
German.bundle/
Info.plist
de.lproj/
Localizable.strings
Localizable.stringsdict
Localizable.strings contains the normal translations:
"Settings" = "Einstellungen";
"Open Project" = "Projekt öffnen";
"Could not remove %@" = "Entfernen von %@ fehlgeschlagen";
Localizable.stringsdict is optional and can provide plural rules. At least one of Localizable.strings or Localizable.stringsdict must exist under the declared language's .lproj directory.
Format placeholders
Every placeholder in a translated value must match the placeholder at the same argument position in its key. Muxy rejects the whole bundle otherwise, because a mismatched placeholder makes Foundation read an argument that was never passed.
"Created branch %@" = "Zweig %@ erstellt"; ✅ same placeholder
"%@ (%@)" = "%2$@ – %1$@"; ✅ reordered with positional placeholders
"%@ (%@)" = "%1$@"; ✅ dropping a trailing argument is allowed
"Created branch %@" = "Zweig %@ %@ erstellt"; ❌ adds an argument the app never passes
"%lld changes" = "%@ Änderungen"; ❌ integer argument read as an object
"Created branch %@" = "Zweig %s erstellt"; ❌ object argument read as a C string
"Settings" = "Einstellungen %@"; ❌ key takes no arguments
Use positional placeholders (%1$@, %2$lld) whenever the translation needs a different word order. %ld and %lld are interchangeable, as are %d and %u with their h/hh variants. For floating-point conversions (aAeEfFgG), the lowercase l modifier is a no-op, so forms such as %lf and %f are interchangeable; uppercase L remains the distinct long-double modifier. %*d-style widths that consume an extra argument are rejected. In Localizable.stringsdict, the same rules apply to NSStringLocalizedFormatKey and to every plural variant inside each variable.
A minimal Info.plist is:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.example.muxy-german.localization</string>
<key>CFBundleDevelopmentRegion</key>
<string>de</string>
</dict>
</plist>
Do not add CFBundleExecutable. Muxy rejects localization bundles that declare executable code, rejects paths that leave the extension directory, validates the property-list catalogs, and limits each catalog to 4 MiB. Muxy reads the bundle only through Foundation's string-localization APIs and never loads it as executable code.
Fallback and lifecycle
Missing translation keys fall back to Muxy's English source text. If the selected provider is disabled, removed, or fails validation, Muxy temporarily uses English while preserving the selection. Enabling the provider again restores it automatically.
Language changes apply immediately to Muxy's native interface, including built-in search indexes and generated status text. Search also retains English and technical terms as fallbacks. Extension-authored webview content, extension names, command titles, and extension settings remain owned by each extension and are not translated by an app-language provider.
The current English Localizable.strings file in the Muxy repository is the source template for translation extensions. Keep format placeholders unchanged and translate the value on the right-hand side.