Skip to content

Latest commit

 

History

History
329 lines (234 loc) · 9.38 KB

file-format.md

File metadata and controls

329 lines (234 loc) · 9.38 KB

File Format Reference

Translation files use the .tkeys extension and follow an INI-inspired format: one entry per line, with optional sections and typed placeholders.

Class directive

Every .tkeys file must start with an @class directive that specifies the generated class name:

@class App

This generates public static partial class App. The directive is required — the file will not compile without it.

Basics

A translation file is a plain text file. Each line is one of:

Line type Example What it does
Class directive @class App Sets the generated class name (required, once per file)
Comment # This is a comment Ignored entirely
Section [Navigation] Groups the keys below it into a nested class
Entry Home : Home Defines a translation key with its fallback text
Blank line (empty) Ignored (use freely for readability)
@class App

# This file defines all user-facing strings for the app

[Navigation]
Home : Home
Settings : Settings

[Errors]
NotFound : Page not found

File naming

The file name is not used for class naming — the @class directive controls the generated class name. You can name files however you like:

File @class directive Generated class
app-strings.tkeys @class App public static partial class App
errors.tkeys @class Errors public static partial class Errors
admin.tkeys @class AdminPanel public static partial class AdminPanel

You can have multiple .tkeys files in one project. Each generates its own class.

Sections

Sections are optional. They group keys into nested static classes:

[Buttons]
Save : Save
Cancel : Cancel

Generates:

public static partial class App
{
    public static partial class Buttons
    {
        public static TranslationString Save { get; } = ...;
        public static TranslationString Cancel { get; } = ...;
    }
}

// Usage:
string label = App.Buttons.Save;

Keys defined before any section go into the root class:

AppName : My App

[Buttons]
Save : Save
App.AppName    // "My App"      (root level)
App.Buttons.Save  // "Save"     (inside section)

Section names must be valid C# identifiers (letters, digits, underscores; no spaces).

Placeholders

Placeholders let you inject values into translation strings. They use curly braces with a name and an optional type (separated by |):

{parameterName}
{parameterName|Type}

When the type is omitted, the parameter defaults to object.

Example:

Greeting : Hello, {userName|string}!
Message : The value is {data}

This generates methods (not properties):

// Generated:
public static TranslationString Greeting(string userName) => ...;
public static TranslationString Message(object data) => ...;

// Usage:
string msg = App.Greeting("Marco");
// "Hello, Marco!"

Multiple placeholders

An entry can have multiple placeholders:

Summary : {userName|string} has {count|int} items worth {total:C2|decimal}
string msg = App.Summary("Marco", 3, 29.99m);
// "Marco has 3 items worth $29.99"

Parameters appear in the generated method in the same order as in the text.

Format specifiers

You can add a format specifier between the name and the type, using : for the format and | for the type:

{parameterName:formatString|Type}

The : separates the name from the format (just like standard .NET formatting), and | separates the format from the type. This means format strings containing colons (like HH:mm:ss) work naturally.

Examples:

# DateTime formatted as hours:minutes
TimeNow : It's {time:HH:mm|DateTime}

# Decimal formatted as currency with 2 decimal places
Price : Total: {price:C2|decimal}

# Integer formatted with thousands separator
BigNumber : Population: {count:N0|int}

The format specifier works like .NET's standard format strings. It is used as the default when formatting the fallback text. Translators can override it in their translation files (see Runtime Configuration).

Supported types

These type names can be used in placeholders:

Type alias .NET type Supports format specifiers
(omitted) System.Object At runtime, if value is IFormattable
object System.Object At runtime, if value is IFormattable
string System.String No
int System.Int32 Yes
long System.Int64 Yes
float System.Single Yes
double System.Double Yes
decimal System.Decimal Yes
bool System.Boolean No
DateTime System.DateTime Yes
DateTimeOffset System.DateTimeOffset Yes
DateOnly System.DateOnly Yes
TimeOnly System.TimeOnly Yes
TimeSpan System.TimeSpan Yes
Guid System.Guid Yes

You can also use fully-qualified type names for types not in this list.

Escaping braces

If you need a literal { or } in your text, double them:

CodeExample : Use {{braces}} to define placeholders

This produces the text: Use {braces} to define placeholders

This works the same way as C# string interpolation escaping.

What generates what

Here's a complete example showing input and output:

Input (App.tkeys):

@class App

# Simple key (no placeholders)
AppName : My Application

[Greetings]
# Simple key inside a section
Hello : Hello!

# One string parameter
Welcome : Welcome, {name|string}!

# Multiple parameters with format specifiers
DetailedGreeting : Good {timeOfDay|string}, {name|string}! It's {time:HH:mm|DateTime}.

# Typeless parameter (defaults to object)
Generic : The value is {data}

Generated API:

// Simple keys become properties
TranslationString name = App.AppName;        // "My Application"
string nameStr = App.AppName;                 // implicit conversion

// Section keys are nested
TranslationString hello = App.Greetings.Hello;  // "Hello!"

// Parameterized keys become methods
TranslationString ts = App.Greetings.Welcome("Marco");  // "Welcome, Marco!"

// Multiple params
string msg = App.Greetings.DetailedGreeting(
    "morning",
    "Marco",
    new DateTime(2026, 2, 7, 9, 30, 0));
// "Good morning, Marco! It's 09:30."

// Typeless params accept any object
string generic = App.Greetings.Generic(42);  // "The value is 42"

Rules and constraints

  1. One entry per line — use \ at the end of a line for continuation (see below)
  2. Keys must be unique within a file (across all sections)
  3. Key names should be valid C# identifiers (PascalCase recommended)
  4. Section names should be valid C# identifiers
  5. Parameter names should be valid C# identifiers (camelCase recommended)
  6. The : separator is the first : on the line — values can contain :
  7. Leading/trailing whitespace around keys and values is trimmed

Multiline values

For long translation text, use \ (backslash) at the end of a line to continue on the next line. Leading whitespace on continuation lines is trimmed:

[Messages]
LongDescription : This is a very long description \
    that spans multiple lines \
    for readability

ShortOne : This fits on one line

The result is a single value: This is a very long description that spans multiple lines for readability.

Notes:

  • The \ must be the last character on the line
  • Leading whitespace on continuation lines is trimmed — add a space before \ if you want a space between parts
  • Backslashes that are not the last character are treated as literal characters (e.g., C:\Users works as expected)
  • Continuation works with placeholders: {name|string} can span across lines

Newline escape

To embed an actual newline character in the translated text, use \n:

[Messages]
Terms : By continuing you agree to:\nRule 1: No spamming\nRule 2: Be respectful

Result (3 lines):

By continuing you agree to:
Rule 1: No spamming
Rule 2: Be respectful

This composes with \ continuation for readability:

Terms : By continuing you agree to:\n\
    Rule 1: No spamming\n\
    Rule 2: Be respectful

Escape sequences:

Sequence Result
\n Newline character
\r Carriage return character
\\ Literal backslash
\ (end of line) Line continuation (join)
\X (other) Kept as-is (e.g., C:\Users is safe)

Both \n and \r\n are normalized to Environment.NewLine at resolve time, so the output matches the current platform regardless of how the newline was written.

To produce a literal \n in the output (not a newline), write \\n.

Generator diagnostics

The source generator reports validation issues directly in the IDE (squiggly underlines in VS/Rider) and in CI build output. No extra tooling required.

ID Severity Condition Example message
TKEYS001 Error Missing @class directive "Translation file 'app.tkeys' is missing a required '@class ClassName' directive"
TKEYS002 Error Duplicate key in same file "Key 'Welcome' is defined more than once"
TKEYS003 Error Unknown placeholder type "Unknown type 'strng' in placeholder {name|strng}"
TKEYS004 Warning Empty section with no entries "Section [Empty] contains no entries"
TKEYS005 Info Key name starts with lowercase "Key 'welcomeMsg' should use PascalCase"