[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6KRBJ2WEAWF2P2XVSHMR8PQ":3,"skill-same:01M6KRBJ2WEAWF2P2XVSHMR8PQ":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":45,"repositoryHtmlUrl":46,"repositoryHomepageUrl":6,"ownerHtmlUrl":47,"ownerBlogUrl":48,"ownerDescription":49,"updatedAt":50,"install":51,"resources":64},"01M6KRBJ2WEAWF2P2XVSHMR8PQ","localization",null,"Use when implementing localization (i18n\u002Fl10n) — TranslationServer, CSV\u002FPO translation files, locale switching, RTL support, and pluralization in Godot 4.3+","jame581\u002FGodotPrompter","jame581","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F21112138?v=4","01M6KR3KEQ9Z30WVJRR2E2ZVXE","Jan Mesarč","01M6KR3KJVRMPDSMASQPBCBVK3",957,"MIT","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000004","电子表格","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36],"translation","implementing","files","switching","i18n","support","locale","l10n","use","csv","godot 4","godot","when","2026-11-10 10:12:30",false,"---\nname: localization\ndescription: Use when implementing localization (i18n\u002Fl10n) — TranslationServer, CSV\u002FPO translation files, locale switching, RTL support, and pluralization in Godot 4.3+\n---\n\n# Localization in Godot 4.3+\n\nAll examples target Godot 4.3+ with no deprecated APIs; GDScript first, then C#.\n\n> **Related skills:** **godot-ui** for Control nodes and theme management, **save-load** for persisting language settings, **responsive-ui** for layout adjustments per locale.\n\n---\n\n## 1. Core Concepts\n\n### How Godot Localization Works\n\n1. **Wrap all user-facing strings** in `tr()` — Godot's translation function\n2. **Create translation files** (CSV or PO) mapping keys to translated strings\n3. **Import translation files** as `Translation` resources\n4. **Switch locale at runtime** via `TranslationServer.set_locale()`\n\nAll `Control` nodes with `text`, `tooltip_text`, or `placeholder_text` properties auto-translate when the value matches a translation key.\n\n### Translation Key Strategies\n\n| Strategy | Example Key | Pros | Cons |\n|----------|-------------|------|------|\n| Semantic keys | `MENU_START_GAME` | Clear intent, easy to find | Needs a default language fallback |\n| English-as-key | `Start Game` | Readable code, no mapping file for English | Breaks if English text changes |\n\n> **Recommendation:** Use semantic keys (`MENU_START_GAME`) for production; English-as-key only for prototypes or solo projects.\n\n---\n\n## 2. Translation Files\n\n### CSV Format\n\nThe simplest format. First column is the key, subsequent columns are locale codes.\n\n```csv\nkeys,en,cs,de,ja\nMENU_START,Start Game,Začít hru,Spiel starten,ゲームスタート\nMENU_OPTIONS,Options,Nastavení,Optionen,オプション\nMENU_QUIT,Quit,Ukončit,Beenden,終了\nPLAYER_HEALTH,Health: %d,Zdraví: %d,Gesundheit: %d,体力: %d\nITEM_COLLECTED,%s collected!,%s sebráno!,%s gesammelt!,%sを入手！\n```\n\nSave as `translations.csv` in your project. Godot auto-detects the format on import.\n\n**Import settings** (Import dock):\n- **Delimiter**: Comma (default), Semicolon, or Tab\n- **Compress**: on by default — stores an `OptimizedTranslation`, skipped for files that use `?context`\u002F`?plural` (§10)\n\n### PO Format (Gettext)\n\nIndustry-standard format, preferred by translation teams and tools like Poedit, Weblate, Crowdin.\n\n**Create a POT template** (`messages.pot`):\n\n```\nmsgid \"MENU_START\"\nmsgstr \"\"\n\nmsgid \"MENU_OPTIONS\"\nmsgstr \"\"\n\nmsgid \"MENU_QUIT\"\nmsgstr \"\"\n\nmsgid \"PLAYER_HEALTH\"\nmsgstr \"\"\n```\n\n**Create locale files** (e.g., `cs.po` for Czech):\n\n```\nmsgid \"MENU_START\"\nmsgstr \"Začít hru\"\n\nmsgid \"MENU_OPTIONS\"\nmsgstr \"Nastavení\"\n\nmsgid \"MENU_QUIT\"\nmsgstr \"Ukončit\"\n\nmsgid \"PLAYER_HEALTH\"\nmsgstr \"Zdraví: %d\"\n```\n\n### Registering Translations\n\n**Project Settings → Localization → Translations → Add...** → select your `.csv` or `.po` files.\n\nOr register at runtime:\n\n```gdscript\nvar translation := load(\"res:\u002F\u002Ftranslations\u002Fcs.po\") as Translation\nTranslationServer.add_translation(translation)\n```\n\n```csharp\nvar translation = GD.Load\u003CTranslation>(\"res:\u002F\u002Ftranslations\u002Fcs.po\");\nTranslationServer.AddTranslation(translation);\n```\n\n> ⚠️ **Changed in Godot 4.7:** `OptimizedTranslation.generate()` now returns `bool` (was `void`). GDScript- and C#-source-compatible, but binary-incompatible — recompile precompiled C# plugins calling it. See the [4.7 migration guide](https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Flatest\u002Ftutorials\u002Fmigrating\u002Fupgrading_to_godot_4.7.html).\n\n### POT Generation Hooks (Godot 4.7+)\n\nA custom `EditorTranslationParserPlugin` can override `_customize_strings()` — called once after all files are parsed during POT generation — to add or remove entries from the final extracted-string list:\n\n```gdscript\n@tool\nextends EditorTranslationParserPlugin\n\nfunc _customize_strings(strings: Array[PackedStringArray]) -> Array[PackedStringArray]:\n    strings.append(PackedStringArray([\"Test 1\", \"context\", \"test 1 plurals\", \"test 1 comment\"]))\n    # Drop internal strings that begin with \"$\".\n    return strings.filter(func(s): return not s[0].begins_with(\"$\"))\n```\n\n```csharp\n#if TOOLS\nusing System.Linq;\nusing Godot;\n\npublic partial class CommentAwareParser : EditorTranslationParserPlugin\n{\n    public override Godot.Collections.Array\u003Cstring[]> _CustomizeStrings(Godot.Collections.Array\u003Cstring[]> strings)\n    {\n        strings.Add(new[] { \"Test 1\", \"context\", \"test 1 plurals\", \"test 1 comment\" });\n        \u002F\u002F Drop internal strings that begin with \"$\".\n        return new Godot.Collections.Array\u003Cstring[]>(strings.Where(s => !s[0].StartsWith(\"$\")));\n    }\n}\n#endif\n```\n\n> **Godot 4.7+:** The POT generator also extracts `Control.accessibility_name` and `accessibility_description`, making accessibility strings translatable without manual listing. ([GH-117134](https:\u002F\u002Fgithub.com\u002Fgodotengine\u002Fgodot\u002Fpull\u002F117134))\n\n---\n\n## 3. Using tr() in Code\n\n### GDScript\n\n```gdscript\n# Basic translation\nvar label_text: String = tr(\"MENU_START\")  # \"Start Game\" or translated equivalent\n\n# With format arguments\nvar health_text: String = tr(\"PLAYER_HEALTH\") % current_health\n# \"Health: 85\" or \"Zdraví: 85\"\n\n# With string arguments\nvar collected_text: String = tr(\"ITEM_COLLECTED\") % item_name\n# \"Sword collected!\" or \"Meč sebráno!\"\n\n# Pluralization (Godot 4.x)\nvar count := 5\nvar msg: String = tr_n(\"ONE_ENEMY\", \"MANY_ENEMIES\", count)\n# Needs plural forms in the translation: PO, or CSV `?plural` rows on Godot 4.6+ (§10)\n```\n\n### C#\n\n```csharp\nstring labelText = Tr(\"MENU_START\");\n\u002F\u002F string.Format ignores GDScript-style %d — substitute it yourself.\nstring healthText = Tr(\"PLAYER_HEALTH\").Replace(\"%d\", currentHealth.ToString());\n\n\u002F\u002F Pluralization\nstring msg = TrN(\"ONE_ENEMY\", \"MANY_ENEMIES\", count);\n```\n\n### Automatic Control Translation\n\n`Label`, `Button`, `RichTextLabel`, and other Control nodes auto-translate their `text` property when it matches a translation key. Set the text to the key:\n\n```\nButton.text = \"MENU_START\"   → displays \"Start Game\" (en) or \"Začít hru\" (cs)\n```\n\n> **Tip:** To disable automatic translation on a specific Control, set `auto_translate_mode` to `DISABLED`.\n\n> **Godot 4.7+:** `Control.translation_context: StringName` sets a per-control translation context, used both to translate displayed text and to generate translation templates — the property equivalent of `tr()`'s context argument (C#: `TranslationContext`). ([GH-115340](https:\u002F\u002Fgithub.com\u002Fgodotengine\u002Fgodot\u002Fpull\u002F115340))\n\n---\n\n## 4. Switching Locale at Runtime\n\n### GDScript\n\n```gdscript\n# Switch language\nfunc set_language(locale_code: String) -> void:\n    TranslationServer.set_locale(locale_code)\n    # All Control nodes with translation keys update automatically\n\n# Get current locale\nvar current: String = TranslationServer.get_locale()  # e.g. \"en\", \"cs\", \"de\"\n\n# Get available locales\nvar locales: PackedStringArray = TranslationServer.get_loaded_locales()\n```\n\n### C#\n\n```csharp\npublic void SetLanguage(string localeCode)\n{\n    TranslationServer.SetLocale(localeCode);\n}\n\nstring current = TranslationServer.GetLocale();\n```\n\n> **Godot 4.8+:** `OS.get_preferred_locales()` (C#: `OS.GetPreferredLocales()`) returns the user's locales in order of preference, each in the form `language_Script_COUNTRY_VARIANT@extra`. For a first-run default, walk it and take the first entry that matches a locale you ship (`TranslationServer.compare_locales()` scores a match), rather than trusting `OS.get_locale()` alone.\n\n### Language Selection Menu\n\n```gdscript\nextends Control\n\n@onready var language_button: OptionButton = %LanguageButton\n\nvar _locales: Array[Dictionary] = [\n    {\"code\": \"en\", \"name\": \"English\"},\n    {\"code\": \"cs\", \"name\": \"Čeština\"},\n    {\"code\": \"de\", \"name\": \"Deutsch\"},\n    {\"code\": \"ja\", \"name\": \"日本語\"},\n]\n\nfunc _ready() -> void:\n    for locale in _locales:\n        language_button.add_item(locale[\"name\"])\n\n    # Set current selection\n    var current_locale: String = TranslationServer.get_locale()\n    for i in _locales.size():\n        if _locales[i][\"code\"] == current_locale:\n            language_button.selected = i\n            break\n\n    language_button.item_selected.connect(_on_language_selected)\n\nfunc _on_language_selected(index: int) -> void:\n    TranslationServer.set_locale(_locales[index][\"code\"])\n    # Save preference — SettingsManager is a user-created autoload (see save-load skill)\n    SettingsManager.set_setting(\"general\", \"locale\", _locales[index][\"code\"])\n```\n\n---\n\n## 5. Right-to-Left (RTL) Support\n\nArabic, Hebrew, and Persian need `layout_direction` on Controls (`LOCALE` auto-follows the current locale), `structured_text_type` so URLs and paths do not fully reverse, and a font covering the script — Godot's default font does not. Re-apply layout direction whenever the locale changes. `TranslationServer` has **no signals** — override `_notification` and watch for `Node.NOTIFICATION_TRANSLATION_CHANGED` (`MainLoop` has a constant of the same name). Since you never subscribe, there is no handler to disconnect.\n\nFull recipes, per-control property table, BBCode for mixed direction, and the C# `LocaleAwarePanel`: [references\u002Frtl-support.md](references\u002Frtl-support.md)\n\n---\n\n## 6. Locale-Aware Formatting\n\nGDScript has no locale-aware digit grouping or date formatting — `\"%d\" % 1234567` is always `1234567`, so you group digits by hand (`TextServer.format_number()` only converts the digits to the locale's numeral system). C# does have it: look up a `CultureInfo` from `TranslationServer.GetLocale()` (swap `_` for `-`) and use `ToString(\"N\"\u002F\"C\"\u002F\"d\", culture)`.\n\nBoth helpers in full: [references\u002Flocale-formatting.md](references\u002Flocale-formatting.md)\n\n---\n\n## 7. Project Organization\n\n### Recommended File Structure\n\n```\nres:\u002F\u002F\n├── translations\u002F\n│   ├── game.csv           # Main game translations\n│   ├── ui.csv             # UI-specific translations\n│   └── items.csv          # Item names and descriptions\n├── fonts\u002F\n│   ├── default_font.ttf   # Latin, Cyrillic\n│   └── cjk_font.ttf       # Chinese, Japanese, Korean\n└── themes\u002F\n    └── default_theme.tres  # Font assignments per locale\n```\n\n### Translation Keys Convention\n\n```\n# Category_Context_Description\nMENU_MAIN_START          # Main menu, start button\nMENU_MAIN_QUIT           # Main menu, quit button\nHUD_HEALTH_LABEL         # In-game HUD, health label\nDIALOGUE_NPC_GREETING    # NPC dialogue, greeting line\nITEM_SWORD_NAME          # Inventory item name\nITEM_SWORD_DESC          # Inventory item description\n```\n\n---\n\n## 8. Common Pitfalls\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Translation key shows instead of text | Translation file not registered in Project Settings | Add to Project Settings → Localization → Translations |\n| Text doesn't update on locale switch | Using string literals instead of `tr()` | Wrap all user-facing strings in `tr()` |\n| Label shows key after scene change | Translation added at runtime by code that has not run yet in this scene | Register translations in Project Settings (not at runtime) |\n| RTL text renders LTR | `layout_direction` not set | Set to `RTL` or `LOCALE` on root Control |\n| Font doesn't display characters | Missing Unicode range in font | Import a font covering the target script (Noto Sans recommended) |\n| Pluralization doesn't work with CSV | Before Godot 4.6 CSV has no plural forms; on 4.6+ the `?plural` column or continuation rows are missing | On 4.6+ add a `?plural` column (§10); before 4.6 use PO |\n| `%s` in translation shows literal `%s` | Using `tr()` result as key instead of formatting it | Use `tr(\"KEY\") % value`, not `tr(\"KEY\" % value)` |\n\n---\n\n## 9. Editor Locale Preview (Godot 4.5+)\n\nGodot 4.5 adds a **Preview Translation** submenu to the 2D editor's **View** menu: the editor viewport re-renders in any loaded locale, so you catch overflow from longer translations and verify RTL layout without entering Play mode. Editor-only — it does not affect exported builds.\n\nSteps and QA benefits: [references\u002Feditor-preview.md](references\u002Feditor-preview.md)\n\n---\n\n## 10. CSV Plural and Context Support (Godot 4.6+)\n\nGodot 4.6 extends CSV translation with two optional header columns — `?context` and `?plural` — plus an optional `?pluralrule` row, bringing context disambiguation and plurals (previously PO-only) to CSV. Extra plural forms go on continuation rows with an empty key, so languages with 3+ forms (Russian, Polish, Arabic) work too. Automatic Control translation skips these rows — call `tr()` \u002F `tr_n()` yourself.\n\nColumn reference, example CSV, and `tr()` \u002F `tr_n()` usage (GDScript + C#): [references\u002Fcsv-plural-context.md](references\u002Fcsv-plural-context.md).\n\n---\n\n## 11. Implementation Checklist\n\n- [ ] All user-facing strings use `tr()` (or are set as translation keys on Control nodes)\n- [ ] Translation files (CSV or PO) are registered in Project Settings → Localization → Translations\n- [ ] Language can be switched at runtime via `TranslationServer.set_locale()`\n- [ ] Language preference is saved and restored on game launch\n- [ ] Fonts cover all target language character sets (Latin, CJK, Arabic, etc.)\n- [ ] RTL languages have `layout_direction` set to `RTL` or `LOCALE` on root UI containers\n- [ ] Format strings (`%s`, `%d`) are applied AFTER `tr()`, not before\n- [ ] Translation keys follow a consistent naming convention\n- [ ] UI layout adapts to longer\u002Fshorter text in different languages (no hardcoded widths)\n- [ ] Plural forms are provided for every target language — PO, or CSV `?plural` rows on Godot 4.6+\n- [ ] Editor Locale Preview (2D editor View → Preview Translation) used for translation QA instead of test runs (Godot 4.5+)\n- [ ] CSV `?context` column used when the same key has different meanings in different UI contexts (Godot 4.6+)\n- [ ] CSV plurals use a `?plural` column with one continuation row per extra plural form (`?pluralrule` row only to override a locale's rule) (Godot 4.6+)\n","# Localization in Godot 4.3+\n\nAll examples target Godot 4.3+ with no deprecated APIs; GDScript first, then C#.\n\n> **Related skills:** **godot-ui** for Control nodes and theme management, **save-load** for persisting language settings, **responsive-ui** for layout adjustments per locale.\n\n---\n\n## 1. Core Concepts\n\n### How Godot Localization Works\n\n1. **Wrap all user-facing strings** in `tr()` — Godot's translation function\n2. **Create translation files** (CSV or PO) mapping keys to translated strings\n3. **Import translation files** as `Translation` resources\n4. **Switch locale at runtime** via `TranslationServer.set_locale()`\n\nAll `Control` nodes with `text`, `tooltip_text`, or `placeholder_text` properties auto-translate when the value matches a translation key.\n\n### Translation Key Strategies\n\n| Strategy | Example Key | Pros | Cons |\n|----------|-------------|------|------|\n| Semantic keys | `MENU_START_GAME` | Clear intent, easy to find | Needs a default language fallback |\n| English-as-key | `Start Game` | Readable code, no mapping file for English | Breaks if English text changes |\n\n> **Recommendation:** Use semantic keys (`MENU_START_GAME`) for production; English-as-key only for prototypes or solo projects.\n\n---\n\n## 2. Translation Files\n\n### CSV Format\n\nThe simplest format. First column is the key, subsequent columns are locale codes.\n\n```csv\nkeys,en,cs,de,ja\nMENU_START,Start Game,Začít hru,Spiel starten,ゲームスタート\nMENU_OPTIONS,Options,Nastavení,Optionen,オプション\nMENU_QUIT,Quit,Ukončit,Beenden,終了\nPLAYER_HEALTH,Health: %d,Zdraví: %d,Gesundheit: %d,体力: %d\nITEM_COLLECTED,%s collected!,%s sebráno!,%s gesammelt!,%sを入手！\n```\n\nSave as `translations.csv` in your project. Godot auto-detects the format on import.\n\n**Import settings** (Import dock):\n- **Delimiter**: Comma (default), Semicolon, or Tab\n- **Compress**: on by default — stores an `OptimizedTranslation`, skipped for files that use `?context`\u002F`?plural` (§10)\n\n### PO Format (Gettext)\n\nIndustry-standard format, preferred by translation teams and tools like Poedit, Weblate, Crowdin.\n\n**Create a POT template** (`messages.pot`):\n\n```\nmsgid \"MENU_START\"\nmsgstr \"\"\n\nmsgid \"MENU_OPTIONS\"\nmsgstr \"\"\n\nmsgid \"MENU_QUIT\"\nmsgstr \"\"\n\nmsgid \"PLAYER_HEALTH\"\nmsgstr \"\"\n```\n\n**Create locale files** (e.g., `cs.po` for Czech):\n\n```\nmsgid \"MENU_START\"\nmsgstr \"Začít hru\"\n\nmsgid \"MENU_OPTIONS\"\nmsgstr \"Nastavení\"\n\nmsgid \"MENU_QUIT\"\nmsgstr \"Ukončit\"\n\nmsgid \"PLAYER_HEALTH\"\nmsgstr \"Zdraví: %d\"\n```\n\n### Registering Translations\n\n**Project Settings → Localization → Translations → Add...** → select your `.csv` or `.po` files.\n\nOr register at runtime:\n\n```gdscript\nvar translation := load(\"res:\u002F\u002Ftranslations\u002Fcs.po\") as Translation\nTranslationServer.add_translation(translation)\n```\n\n```csharp\nvar translation = GD.Load\u003CTranslation>(\"res:\u002F\u002Ftranslations\u002Fcs.po\");\nTranslationServer.AddTranslation(translation);\n```\n\n> ⚠️ **Changed in Godot 4.7:** `OptimizedTranslation.generate()` now returns `bool` (was `void`). GDScript- and C#-source-compatible, but binary-incompatible — recompile precompiled C# plugins calling it. See the [4.7 migration guide](https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Flatest\u002Ftutorials\u002Fmigrating\u002Fupgrading_to_godot_4.7.html).\n\n### POT Generation Hooks (Godot 4.7+)\n\nA custom `EditorTranslationParserPlugin` can override `_customize_strings()` — called once after all files are parsed during POT generation — to add or remove entries from the final extracted-string list:\n\n```gdscript\n@tool\nextends EditorTranslationParserPlugin\n\nfunc _customize_strings(strings: Array[PackedStringArray]) -> Array[PackedStringArray]:\n    strings.append(PackedStringArray([\"Test 1\", \"context\", \"test 1 plurals\", \"test 1 comment\"]))\n    # Drop internal strings that begin with \"$\".\n    return strings.filter(func(s): return not s[0].begins_with(\"$\"))\n```\n\n```csharp\n#if TOOLS\nusing System.Linq;\nusing Godot;\n\npublic partial class CommentAwareParser : EditorTranslationParserPlugin\n{\n    public override Godot.Collections.Array\u003Cstring[]> _CustomizeStrings(Godot.Collections.Array\u003Cstring[]> strings)\n    {\n        strings.Add(new[] { \"Test 1\", \"context\", \"test 1 plurals\", \"test 1 comment\" });\n        \u002F\u002F Drop internal strings that begin with \"$\".\n        return new Godot.Collections.Array\u003Cstring[]>(strings.Where(s => !s[0].StartsWith(\"$\")));\n    }\n}\n#endif\n```\n\n> **Godot 4.7+:** The POT generator also extracts `Control.accessibility_name` and `accessibility_description`, making accessibility strings translatable without manual listing. ([GH-117134](https:\u002F\u002Fgithub.com\u002Fgodotengine\u002Fgodot\u002Fpull\u002F117134))\n\n---\n\n## 3. Using tr() in Code\n\n### GDScript\n\n```gdscript\n# Basic translation\nvar label_text: String = tr(\"MENU_START\")  # \"Start Game\" or translated equivalent\n\n# With format arguments\nvar health_text: String = tr(\"PLAYER_HEALTH\") % current_health\n# \"Health: 85\" or \"Zdraví: 85\"\n\n# With string arguments\nvar collected_text: String = tr(\"ITEM_COLLECTED\") % item_name\n# \"Sword collected!\" or \"Meč sebráno!\"\n\n# Pluralization (Godot 4.x)\nvar count := 5\nvar msg: String = tr_n(\"ONE_ENEMY\", \"MANY_ENEMIES\", count)\n# Needs plural forms in the translation: PO, or CSV `?plural` rows on Godot 4.6+ (§10)\n```\n\n### C#\n\n```csharp\nstring labelText = Tr(\"MENU_START\");\n\u002F\u002F string.Format ignores GDScript-style %d — substitute it yourself.\nstring healthText = Tr(\"PLAYER_HEALTH\").Replace(\"%d\", currentHealth.ToString());\n\n\u002F\u002F Pluralization\nstring msg = TrN(\"ONE_ENEMY\", \"MANY_ENEMIES\", count);\n```\n\n### Automatic Control Translation\n\n`Label`, `Button`, `RichTextLabel`, and other Control nodes auto-translate their `text` property when it matches a translation key. Set the text to the key:\n\n```\nButton.text = \"MENU_START\"   → displays \"Start Game\" (en) or \"Začít hru\" (cs)\n```\n\n> **Tip:** To disable automatic translation on a specific Control, set `auto_translate_mode` to `DISABLED`.\n\n> **Godot 4.7+:** `Control.translation_context: StringName` sets a per-control translation context, used both to translate displayed text and to generate translation templates — the property equivalent of `tr()`'s context argument (C#: `TranslationContext`). ([GH-115340](https:\u002F\u002Fgithub.com\u002Fgodotengine\u002Fgodot\u002Fpull\u002F115340))\n\n---\n\n## 4. Switching Locale at Runtime\n\n### GDScript\n\n```gdscript\n# Switch language\nfunc set_language(locale_code: String) -> void:\n    TranslationServer.set_locale(locale_code)\n    # All Control nodes with translation keys update automatically\n\n# Get current locale\nvar current: String = TranslationServer.get_locale()  # e.g. \"en\", \"cs\", \"de\"\n\n# Get available locales\nvar locales: PackedStringArray = TranslationServer.get_loaded_locales()\n```\n\n### C#\n\n```csharp\npublic void SetLanguage(string localeCode)\n{\n    TranslationServer.SetLocale(localeCode);\n}\n\nstring current = TranslationServer.GetLocale();\n```\n\n> **Godot 4.8+:** `OS.get_preferred_locales()` (C#: `OS.GetPreferredLocales()`) returns the user's locales in order of preference, each in the form `language_Script_COUNTRY_VARIANT@extra`. For a first-run default, walk it and take the first entry that matches a locale you ship (`TranslationServer.compare_locales()` scores a match), rather than trusting `OS.get_locale()` alone.\n\n### Language Selection Menu\n\n```gdscript\nextends Control\n\n@onready var language_button: OptionButton = %LanguageButton\n\nvar _locales: Array[Dictionary] = [\n    {\"code\": \"en\", \"name\": \"English\"},\n    {\"code\": \"cs\", \"name\": \"Čeština\"},\n    {\"code\": \"de\", \"name\": \"Deutsch\"},\n    {\"code\": \"ja\", \"name\": \"日本語\"},\n]\n\nfunc _ready() -> void:\n    for locale in _locales:\n        language_button.add_item(locale[\"name\"])\n\n    # Set current selection\n    var current_locale: String = TranslationServer.get_locale()\n    for i in _locales.size():\n        if _locales[i][\"code\"] == current_locale:\n            language_button.selected = i\n            break\n\n    language_button.item_selected.connect(_on_language_selected)\n\nfunc _on_language_selected(index: int) -> void:\n    TranslationServer.set_locale(_locales[index][\"code\"])\n    # Save preference — SettingsManager is a user-created autoload (see save-load skill)\n    SettingsManager.set_setting(\"general\", \"locale\", _locales[index][\"code\"])\n```\n\n---\n\n## 5. Right-to-Left (RTL) Support\n\nArabic, Hebrew, and Persian need `layout_direction` on Controls (`LOCALE` auto-follows the current locale), `structured_text_type` so URLs and paths do not fully reverse, and a font covering the script — Godot's default font does not. Re-apply layout direction whenever the locale changes. `TranslationServer` has **no signals** — override `_notification` and watch for `Node.NOTIFICATION_TRANSLATION_CHANGED` (`MainLoop` has a constant of the same name). Since you never subscribe, there is no handler to disconnect.\n\nFull recipes, per-control property table, BBCode for mixed direction, and the C# `LocaleAwarePanel`: [references\u002Frtl-support.md](references\u002Frtl-support.md)\n\n---\n\n## 6. Locale-Aware Formatting\n\nGDScript has no locale-aware digit grouping or date formatting — `\"%d\" % 1234567` is always `1234567`, so you group digits by hand (`TextServer.format_number()` only converts the digits to the locale's numeral system). C# does have it: look up a `CultureInfo` from `TranslationServer.GetLocale()` (swap `_` for `-`) and use `ToString(\"N\"\u002F\"C\"\u002F\"d\", culture)`.\n\nBoth helpers in full: [references\u002Flocale-formatting.md](references\u002Flocale-formatting.md)\n\n---\n\n## 7. Project Organization\n\n### Recommended File Structure\n\n```\nres:\u002F\u002F\n├── translations\u002F\n│   ├── game.csv           # Main game translations\n│   ├── ui.csv             # UI-specific translations\n│   └── items.csv          # Item names and descriptions\n├── fonts\u002F\n│   ├── default_font.ttf   # Latin, Cyrillic\n│   └── cjk_font.ttf       # Chinese, Japanese, Korean\n└── themes\u002F\n    └── default_theme.tres  # Font assignments per locale\n```\n\n### Translation Keys Convention\n\n```\n# Category_Context_Description\nMENU_MAIN_START          # Main menu, start button\nMENU_MAIN_QUIT           # Main menu, quit button\nHUD_HEALTH_LABEL         # In-game HUD, health label\nDIALOGUE_NPC_GREETING    # NPC dialogue, greeting line\nITEM_SWORD_NAME          # Inventory item name\nITEM_SWORD_DESC          # Inventory item description\n```\n\n---\n\n## 8. Common Pitfalls\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Translation key shows instead of text | Translation file not registered in Project Settings | Add to Project Settings → Localization → Translations |\n| Text doesn't update on locale switch | Using string literals instead of `tr()` | Wrap all user-facing strings in `tr()` |\n| Label shows key after scene change | Translation added at runtime by code that has not run yet in this scene | Register translations in Project Settings (not at runtime) |\n| RTL text renders LTR | `layout_direction` not set | Set to `RTL` or `LOCALE` on root Control |\n| Font doesn't display characters | Missing Unicode range in font | Import a font covering the target script (Noto Sans recommended) |\n| Pluralization doesn't work with CSV | Before Godot 4.6 CSV has no plural forms; on 4.6+ the `?plural` column or continuation rows are missing | On 4.6+ add a `?plural` column (§10); before 4.6 use PO |\n| `%s` in translation shows literal `%s` | Using `tr()` result as key instead of formatting it | Use `tr(\"KEY\") % value`, not `tr(\"KEY\" % value)` |\n\n---\n\n## 9. Editor Locale Preview (Godot 4.5+)\n\nGodot 4.5 adds a **Preview Translation** submenu to the 2D editor's **View** menu: the editor viewport re-renders in any loaded locale, so you catch overflow from longer translations and verify RTL layout without entering Play mode. Editor-only — it does not affect exported builds.\n\nSteps and QA benefits: [references\u002Feditor-preview.md](references\u002Feditor-preview.md)\n\n---\n\n## 10. CSV Plural and Context Support (Godot 4.6+)\n\nGodot 4.6 extends CSV translation with two optional header columns — `?context` and `?plural` — plus an optional `?pluralrule` row, bringing context disambiguation and plurals (previously PO-only) to CSV. Extra plural forms go on continuation rows with an empty key, so languages with 3+ forms (Russian, Polish, Arabic) work too. Automatic Control translation skips these rows — call `tr()` \u002F `tr_n()` yourself.\n\nColumn reference, example CSV, and `tr()` \u002F `tr_n()` usage (GDScript + C#): [references\u002Fcsv-plural-context.md](references\u002Fcsv-plural-context.md).\n\n---\n\n## 11. Implementation Checklist\n\n- [ ] All user-facing strings use `tr()` (or are set as translation keys on Control nodes)\n- [ ] Translation files (CSV or PO) are registered in Project Settings → Localization → Translations\n- [ ] Language can be switched at runtime via `TranslationServer.set_locale()`\n- [ ] Language preference is saved and restored on game launch\n- [ ] Fonts cover all target language character sets (Latin, CJK, Arabic, etc.)\n- [ ] RTL languages have `layout_direction` set to `RTL` or `LOCALE` on root UI containers\n- [ ] Format strings (`%s`, `%d`) are applied AFTER `tr()`, not before\n- [ ] Translation keys follow a consistent naming convention\n- [ ] UI layout adapts to longer\u002Fshorter text in different languages (no hardcoded widths)\n- [ ] Plural forms are provided for every target language — PO, or CSV `?plural` rows on Godot 4.6+\n- [ ] Editor Locale Preview (2D editor View → Preview Translation) used for translation QA instead of test runs (Godot 4.5+)\n- [ ] CSV `?context` column used when the same key has different meanings in different UI contexts (Godot 4.6+)\n- [ ] CSV plurals use a `?plural` column with one continuation row per extra plural form (`?pluralrule` row only to override a locale's rule) (Godot 4.6+)\n",{"name":5,"description":7},"master","60c28fdc40706a24576731e9fb3508b9c95258ad","skills\u002Flocalization",63,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter","https:\u002F\u002Fgithub.com\u002Fjame581","https:\u002F\u002Fjanmesarc.online\u002F","Software engineer. My biggest passion is game development.","2026-11-13 02:28:46",{"source":8,"skillName":5,"commands":52,"prompt":62,"zipAvailable":63},[53,56,59],{"tool":54,"command":55},"npx","npx skills add jame581\u002FGodotPrompter --skill localization",{"tool":57,"command":58},"bunx","bunx skills add jame581\u002FGodotPrompter --skill localization",{"tool":60,"command":61},"pnpm","pnpm dlx skills add jame581\u002FGodotPrompter --skill localization","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002FSKILL.md",true,[65,71,75,79],{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"references\u002Fcsv-plural-context.md","REFERENCE","参考文档",2437,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002Freferences\u002Fcsv-plural-context.md",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"references\u002Feditor-preview.md",1011,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002Freferences\u002Feditor-preview.md",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"references\u002Flocale-formatting.md",1205,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002Freferences\u002Flocale-formatting.md",{"path":80,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Frtl-support.md",3916,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Flocalization\u002Freferences\u002Frtl-support.md",[84,102,123,151,175,192],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":90,"publishedAt":37},"01M6KRFE5BR2PSD08ZKDAS66R7","xr-development","Use when building VR\u002FAR\u002FXR applications — OpenXR setup, XROrigin3D, hand tracking, controllers, passthrough, and Meta Quest deployment in Godot 4.3+","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fxr-development\u002FSKILL.md",[],[91,92,36,93,35,94,95,96,34,97,98,32,99,100,101],"xr development","development","deployment","vr","setup","tracking","ar","hand","Meta","applications","quest",{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":37},"01M6KRF8GZTG1TAS506MFBSGFC","web-services","Use when a Godot game calls a web server — HTTPRequest, downloads, REST\u002FJSON APIs, auth headers, WebSocketPeer clients, TLS, retries, and keeping secrets out of the build. Not for game networking\u002FRPCs (multiplayer-basics) or browser export (web-export)","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-services\u002FSKILL.md",[],[109,110,111,112,113,114,115,116,32,36,35,117,118,119,120,121,122],"web services","web","services","tls","retries","calling","auth","downloads","clients","headers","secrets","json","build","REST",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":132,"publishedAt":37},"01M6KRF38Q4WSKAYFS56PG87T0","web-export","Use when shipping a Godot game to the browser — Web export preset options, threads vs no-threads and COOP\u002FCOEP headers, hosting on itch.io or your own server, PWA, JavaScriptBridge, user:\u002F\u002F persistence, audio and input limits, and load size","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-export\u002FSKILL.md",[129],{"categoryNo":130,"categoryName":131,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",[110,133,134,135,136,137,138,139,118,140,141,36,142,143,144,145,146,147,32,148,149,150],"export","web export","threads","limits","shipping","size","options","hosting","input","Coop","preset","browser","game","io","load","pwa","Audio","server",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":165,"publishedAt":37},"01M6KREYFETEBH62ZTN6EZH2X3","using-godot-prompter","Bootstrap skill — establishes how to find and use GodotPrompter skills, with platform-specific tool mapping","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fusing-godot-prompter\u002FSKILL.md",[157,160],{"categoryNo":158,"categoryName":159,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":161,"categoryName":162,"taxonomyType":163,"taxonomyTypeDesc":164},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[35,166,167,168,169,32,170,171,172,173,174],"using godot prompter","find","with","bootstrap","tool","platform","mapping","how","how to",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":181,"publishedAt":37},"01M6KRER5G7EQWKGAFN8QJKQ69","tween-animation","Use when implementing tweens — property animation, method tweening, chaining, parallel sequences, easing, and common UI\u002Fgameplay motion recipes","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Ftween-animation\u002FSKILL.md",[],[182,183,32,184,185,186,187,188,189,190,25,36,191],"tween animation","Animation","motion","parallel","gameplay","chaining","ui","recipes","common","sequences",{"skillNo":193,"name":194,"nameZh":6,"description":195,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":196,"categories":197,"keywords":198,"publishedAt":37},"01M6KREKGCQMVDV8WX7HWX8BZG","state-machine","Use when building a state machine or refactoring logic into one — enum, node, and resource-based FSM patterns with trade-offs. Not for adding states to an existing one","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fstate-machine\u002FSKILL.md",[],[199,200,201,35,36,202,203,204,205,206,168,32,25,207],"state","state machine","machine","patterns","node","fsm","state machines","trade","trade-offs"]