Skip to content
SKitLs-dev edited this page Aug 20, 2024 · 9 revisions

Package version (last review): 3.2.4

SKitLs.Utils.Localizations

The project provides mechanisms for automatic localization of your application content. Using the project's capabilities, instead of hard-coded strings, you can replace text interface elements in the code with their keys and move all string values ​​into separate localization packages. Keys will be automatically resolved in the required language and substituted before display.

RU Home Page Available

Contents

Usage Example

Create Language Pack

At the moment only JSON packs are available. To create one use next pattern: {lang}*.json where {lang} is IETF language tag. You can type anything instead of '*' char (ex. en.app.json).

Language pack contains dictionary with the pattern: "key": "value". Values supports {0} tags for formatting.

Here is example packs content:

en.json

{
  "typeLang": "Type language key (or {0}):",
  "exitKey": "exit",
  "example": "Language switched to English",
  "formatMe": "You have typed: \"{0}\"",

  "excep.LangNotSupported": "This language is not supported"
}

es.json

{
  "typeLang": "Escriba la clave de idioma (o {0}):",
  "exitKey": "salir",
  "example": "Idioma cambiado a español",
  "formatMe": "Has escrito: \"{0}\"",

  "excep.LangNotSupported": "El idioma ingresado no es compatible"
}

ru.json

{
  "typeLang": "Введите язык (или {0}):",
  "exitKey": "выйти",
  "example": "Язык сменён на русский",
  "formatMe": "Вы ввели: \"{0}\"",

  "excep.LangNotSupported":  "Введённый язык не поддерживается"
}

Access Language Packs

To get langpacks initialize a new instance of ILocalizator. The only implementation at the moment is StoredLocalizator. Create one, using the path to your saved packs.

var storage = new StoredLocalizator("locals");

In this example your projects' hierarchy could look like this:

  • YourProject
    • /locals
      • en.json
      • es.json
      • ru.json
    • Program.cs

Resolve Strings

To resolve appropriate string use ILocalizator.ResolveString(...) or ILocalizator.ResolveStringOrFallback(...) methods. You can pass params string[] to format resolved string.

var helloMessage = storage.ResolveString(
    lang: LanguageCode.EN,
    key: "typeLang",
    format: exitKey);

Assuming exitKey = "exit" this code will return Type language key (or exit): (based on language pack).

See Program Example section to get the real code for clarity.

Back to Contents

Model

Type Member Description
enum LanguageCode Represents some of the more commonly used primary language subtags.
interface ILocalizator Provides a specialized mechanism for localizing strings based on the given language key.
class StoredLocalizator ILocalizator default implementation. Optimizes performance by preloading all localizations.
static class LangHelper Provides helper methods for working with language codes.

ILocalizator

Type Member Description
string NotDefinedKey The key for the "NotDefined" message.
LanguageCode DefaultLanguage The default language code.
string LocalsPath The path to the localization resource files.
string? ResolveString(...) Resolves the localized string for the specified language key and key identifier.
string ResolveStringOrFallback(...) Resolves the localized string for the specified language key and key identifier or returns a fallback string.

StoredLocalizator

The StoredLocalizator is ILocalizator default implementation.

Optimizes performance by preloading all localizations to the Dictionary<string, Dictionary<LanguageCode, string>> Localizations during initialization. Trading memory consumption for increased speed.

GateLocalizator

The GateLocalizator is ILocalizator default implementation.

Reduces memory load by resolving the required dictionary during run-time. Degrades processing speed.

FastAccessLocalizator

Not Implemented Yet

Stores part of the most used strings in-memory.

See Localizators Performance Comparison.

LangHelper

Special static class, provides helper methods for working with language codes.

Type Member Description
LanguageCode FromTag(string tag) Converts a language tag to a corresponding LanguageCode enumeration value.

Back to contents

Adding localization to your package

This guide will walk you through the steps to add localization files to your .NET library before publishing it as a NuGet package. We strongly recommend placing your localization files in the Resources/Locals directory of your project.

Step 1: Create the Localization Files

Create a Folder for Localization Files:

  • In your project directory, create a folders named Resources/Locals if it doesn't already exist.
  • Add your localization files (e.g., 'en.name.json', 'fr.name.json', etc.) to the Resources/Locals directory.

Your project structure should look like this:

ProjectRoot/
├── Resources/
│   └── Locals/
│       ├── en.json
│       ├── fr.json
│       └── ...
└── ...

Step 2: Update Your .csproj File

To ensure that the localization files are included in the NuGet package, you need to update your .csproj file. This will ensure that the files are packed correctly and are available in the right location when your package is installed.

Add the following XML snippet to your .csproj file:

  <ItemGroup>
    <Content Include="Resources/Locals/YOUR-LANGUAGE-PACK.json">
      <Pack>true</Pack>
      <PackagePath>contentFiles\any\any\Resources\Locals\</PackagePath>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

Explanation:

  • Include="Resources/Locals/*.json": This line includes all JSON files in the Resources/Locals folder.
  • <Pack>true</Pack>: This ensures that the files are packed into the NuGet package.
  • <PackagePath>contentFiles\any\any\Resources\Locals\</PackagePath>: This specifies the path inside the NuGet package where the files will be placed. The files will be accessible in the Resources/Locals folder when the package is installed.
  • <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>: This ensures that the files are copied to the output directory during the build process.

Step 3: Test Your Package

Before publishing, test your package locally to ensure that the localization files are included and correctly placed. You can do this by creating a NuGet package locally and inspecting its contents.

Recommendations for Implementation and Naming

When adding localization to your package, it's crucial to follow best practices to ensure that your package is easy to maintain, integrate, and use. Below are some key recommendations:

Always Include an English Localization

We strongly recommend that every package includes an English ('en') localization file. This ensures that your package can be easily ported and understood by a global audience. English is often the default language for many systems, and providing an English localization can help avoid potential issues when the package is used in different regions.

Use Clear and Unique File Naming Conventions

To avoid conflicts and ensure clarity, we suggest using a standardized naming convention for your localization files. A good naming pattern is:

lang.project.author.json

For example:

  • en.myproject.johndoe.json for English localization
  • fr.myproject.johndoe.json for French localization
  • etc

This pattern helps in clearly identifying the language, project, and author, reducing the chances of file name conflicts with other packages.

Use Complex Keys for Localization Strings

To further avoid conflicts, especially when your package might be integrated into larger systems or projects, use fully qualified keys in your localization files. This approach ensures that keys are unique and descriptive. Instead of using simple keys like "greeting": "Hello" use complex, namespaced keys like: "johndoe.myproject.greeting": "Hello".

This method follows the format: {author}.{project}.{key}

For example:

{
  "johndoe.myproject.welcomeMessage": "Welcome to My Project"
}

By using this pattern, you ensure that your localization keys are unique and can coexist with other localization files in the same project, reducing the risk of accidental overwrites or key conflicts.

Back to contents

Program Example

The idea

A localized console interface that allows you to change your language.

Details

This code in a while (true) loop will ask the user to enter the language code.

After changing the language, the program will notify about this and update the default language.

If the language is not supported, an error will be thrown with a localized message code.

Code

v.3.0.1

    internal class Program
    {
        static void Main(string[] args)
        {
            // Initialize new ILocalizator
            var storage = new StoredLocalizator("locals");

            while (true)
            {
                try
                {
                    // Get localization for the "exit" key
                    // Use null-lang to use default one
                    var exitKey = storage.ResolveString(
                        lang: null,
                        key: "exitKey",
                        resolveDefault: true)
                        ?? throw new ArgumentException("excep.LangNotSupported");

                    // Get Hello-Message and format with "exit" button
                    var helloMessage = storage.ResolveString(
                        lang: null,
                        key: "typeLang",
                        resolveDefault: true,
                        format: exitKey);
                    Console.WriteLine(helloMessage);

                    // Get input
                    var input = Console.ReadLine();

                    if (string.IsNullOrEmpty(input))
                        continue;
                    if (input.Equals(exitKey, StringComparison.CurrentCultureIgnoreCase))
                        break;

                    // Get lang enum from input tag
                    var lang = LangHelper.FromTag(input.Replace("+", ""));

                    string? resolved = null;
                    if (input.Contains('+'))
                    {
                        resolved = storage.ResolveStringOrFallback(lang, "notdef", true);
                    }
                    else
                    {
                        resolved = storage.ResolveString(lang, "example", false) ?? throw new ArgumentException("excep.LangNotSupported");
                        
                        // Update default language
                        storage.DefaultLanguage = lang;
                    }
                    
                    Console.WriteLine(resolved);
                }
                catch (ArgumentException e)
                {
                    var errorMes = storage.ResolveString(LanguageCode.EN, e.Message, true);
                    Console.WriteLine(errorMes);
                }
            }
        }
    }

Outputs

Output example

Back to Contents

Localizators Performance Comparison

The graphs below compare speed and memory usage for StoredLocalizator and GateLocalizator depending on the amount of localized strings.

Speed Comparison

Memory Comparison

Source Calculations

Back to Contents