Localization with CSV
UTF-8 tables, contexts, Russian plural forms and Label refresh.
A CSV table stores message keys in its first column and locales in the header. Reckfell imports one .translation resource per language. Register these resources as described in translation setup.
A table that imports correctly
Save Locale/ui.csv as UTF-8. These examples use commas and no byte order mark. Export the actual CSV file from your spreadsheet, not an XLSX file renamed to .csv.
keys,en,ru,_note
PLAY,Play,Играть,Main menu
ONLY_EN,English fallback,,Intentionally missing in ru
QUOTE,"He said ""Hello, friend"".","Он сказал «Привет, друг».",CSV quoting
LINES,Line one\nLine two,Строка один\nСтрока два,Escaped newline- The first header cell is only a label. en and ru identify locale columns; keep identifiers exact and do not add surrounding spaces.
- A header starting with an underscore, such as _note, marks an ignored comment column.
- Keys are case-sensitive. Keep each key/context pair unique. Do not rely on duplicate rows overriding each other.
- Enclose fields containing delimiters, quotes or literal newlines in double quotes. Represent an embedded double quote as two quotes.
- An empty translation cell is skipped; lookup can use the fallback language.
The QUOTE row preserves both the comma and embedded quotes. With default settings, LINES contains a real newline after import.
Import options
Select the CSV in Content Browser, change its Import settings and apply Reimport.
| Setting | Values and effect |
|---|---|
| delimiter | 0: comma; 1: semicolon; 2: tab. Must match the file |
| compress | 0: Disabled; 1: Auto. Auto optimizes simple tables; actual use of contexts or plural forms keeps an ordinary Translation resource |
| unescape_keys | false by default: preserve backslash sequences in keys |
| unescape_translations | true by default: interpret sequences such as \n. Disable when a backslash must remain literal |
Changing an option does not change the source CSV. Reimport applies it and regenerates the language resources. Treat .translation files generated from CSV as output: edit the CSV, not those resources.
Distinguish the same word by context
Add a ?context column. This is a complete separate table:
keys,?context,en,ru
Open,verb,Open,Открыть
Open,state,Open,ОткрытоInside a method, TranslationServer.Translate("Open", "state").ToString() returns Открыто with locale ru. Context is part of the lookup identity: an unknown context does not match an arbitrary context row. For an automatic Control translation, use its TranslationContext property; for computed text pass the context explicitly.
Select plural forms before formatting
Save this complete table as Locale/forms.csv and register both generated resources alongside ui resources:
keys,?plural,?context,en,ru
Open,,verb,Open,Открыть
Open,,state,Open,Открыто
ITEM,ITEMS,,{0} item,{0} предмет
,,,{0} items,{0} предмета
,,,,{0} предметовThe first plural row supplies the singular key and the plural source key. Continuation rows leave keys, ?plural and ?context empty. Supply forms in the locale's rule order: English has two; Russian has three. Do not duplicate English into a third cell merely to fill the table.
TranslatePlural("ITEM", "ITEMS", n) selects a form. It does not substitute n. With ru, n=1 selects {0} предмет, 2 selects {0} предмета, 5 and 11 select {0} предметов, and 21 selects {0} предмет. Format the selected string afterward. The {0} placeholder must remain valid in every translation.
For a deliberate custom rule, a ?pluralrule row can set that language's nplurals/plural expression. The built-in ru/en rules suffice here; prefer them to an unnecessary override.
A Label that updates when the language changes
Attach this complete LocaleDemo.cs to a Label. It requires forms.en.translation and forms.ru.translation in the project's translations array. Call SetGameLocale("ru") or SetGameLocale("en") from your language selector. After changing ItemCount, call RefreshText.
using Reckfell;
public partial class LocaleDemo : Label
{
public int ItemCount { get; set; } = 5;
public override void _Ready()
{
AutoTranslateMode = AutoTranslateModeEnum.Disabled;
RefreshText();
}
public override void _Notification(int what)
{
if (what == NotificationTranslationChanged && IsInsideTree())
RefreshText();
}
public void RefreshText()
{
// TranslatePlural selects a form; it does not insert the number.
string form = TranslationServer.TranslatePlural("ITEM", "ITEMS", ItemCount).ToString();
Text = string.Format(form, ItemCount);
}
public void SetGameLocale(string locale)
{
TranslationServer.SetLocale(locale);
}
}Automatic translation is disabled here because Text already contains the calculated result. The script uses TranslationServer explicitly. Label.Language controls text shaping; it is not the global game locale. SetGameLocale therefore calls TranslationServer.SetLocale instead.
Diagnose an import
| Symptom | Action |
|---|---|
| No language resources | Check UTF-8, locale headers, delimiter and importer messages |
| Entire row appears as one field | Select the delimiter actually used by the file and reimport |
| Literal \n or an unexpected newline | Check unescape_translations; do not confuse CSV quoting with backslash escaping |
| Wrong plural form or missing form | Check continuation rows, locale, form order and preserved placeholders |
| Runtime still shows a key | Register generated resources; verify case, context and fallback |
Native tests cover import, quoting, Unicode, escapes, semicolon configuration, compression behavior, contexts, ru/en plural selection and the exact C# example. UI interaction, tab-delimited input, custom CSV plural-rule overrides, rendered typography and exports are not covered by these tests.