slang Localization Library

repository·main·Indexed 20 days ago

https://github.com/slang-i18n/slang

A localization library featuring type-safe translations. It includes slang_flutter for Flutter integration with BuildContext extensions and TranslationProvider, slang_build_runner for code generation via build.yaml, and slang_gpt for providing context-aware translations using OpenAI's GPT models at compile time.

Tokens
14.5K
Snippets
65
Records
83
Agent score
65%

What's inside slang

  1. Manage GPT context length and input size

    main

    Each GPT model has a specific context length. If the model starts to 'forget' context (especially with less common languages), you can:

    1. Reduce the max_input_length in your configuration to split the input into smaller, more manageable requests.
    2. Use a model with a larger context length (e.g., gpt-4o or gpt-4o-mini which support 128,000 tokens).
  2. Organize translations using Namespaces

    main

    You can split your translations into different files. Each file represents a separate namespace. This helps manage large translation projects by grouping related keys together (e.g., widgets vs dialogs).

    i18n/
      widgets/
        - widgets.i18n.json
        - widgets_fr.i18n.json
      dialogs/
        - dialogs.i18n.json
        - dialogs_fr.i18n.json
  3. Use Dependency Injection for translations

    main

    Starting from version 5.9.0, plural resolvers are part of the translation class. This allows you to build your own instances without relying on LocaleSettings or other side effects, which is ideal for Dependency Injection (e.g., using Riverpod).

    Instead of using the global LocaleSettings, you can use the .build() method on your AppLocale to create specific instances with custom cardinalResolvers.

    // riverpod example
    final english = AppLocale.en.build(cardinalResolver: myEnResolver);
    final german = AppLocale.de.build(cardinalResolver: myDeResolver);
    final translationProvider = StateProvider<StringsEn>((ref) => german);
    
    // access the current instance
    final t = ref.watch(translationProvider);
    String a = t.welcome.title;
  4. How attribute inference works for interfaces

    main

    Slang automatically infers the attributes of an interface based on the data found at the specified paths or via modifiers.

    If multiple nodes are assigned the same interface name, their attributes are merged. If a property exists in one node but is missing in another, it is treated as optional (?) in the final generated interface.

    Example of merging: If first(interface=MyInterface) contains keys a and b, and second(interface=MyInterface) contains keys a and c, the resulting MyInterface will be inferred as having a, b?, c?.

    {
      "first(interface=MyInterface)": {
        "i": { "a": "", "b": "" },
        "j": { "a": "", "b": "", "c": "" }
      },
      "second(interface=MyInterface)": {
        "k": { "a": "" },
        "l": { "a": "", "c": "" }
      }
    }
  5. Use Interfaces for type-safe translation nodes

    main

    To avoid using List<dynamic> or Map<String, dynamic>, you can create common super classes (interfaces) for different nodes in your JSON. This provides better type safety and autocompletion in Dart.

    {
      "pages": [
        {
          "title": "E2E encryption",
          "content": "Your data is safe!"
        },
        {
          "title": "Sync",
          "content": "Synchronize all your devices!"
        }
      ]
    }

    With the generated mixin:

    mixin PageData {
      String get title;
      String get content;
    }
  6. Use interfaces to create common superclasses for structured maps

    main

    When multiple translation maps share the same structure, you can use Interfaces to generate a common superclass (as a Dart mixin). This allows you to write cleaner, more type-safe code by treating different translation nodes as instances of the same interface.

    To use an interface, add the (interface=<Interface Name>) modifier to a container node in your translation file.

    {
      "onboarding": {
        "whatsNew(interface=ChangeData)": {
          "v2": {
            "title": "New in 2.0",
            "rows": [
              "Add sync"
            ]
          }
        }
      }
    }

    This generates a mixin like:

    mixin ChangeData {
      String get title;
      List<String> get rows;
    }
  7. Configure interfaces using modifiers

    main

    The quickest way to implement interfaces is by using modifiers directly in your translation keys. This method automatically infers the required attributes from the data structure.

    • (interface=MyInterface): Apply this to a container (a map or a list) to target multiple nodes that should follow the same interface.
    • (singleInterface=MyInterface): Apply this to a single map to target one specific node. This cannot be applied to lists.
    {
      "whatsNew(interface=ChangeData)": { ... }
    }
  8. Use the slang CLI

    main

    The slang tool is a command-line interface used to manage and generate Dart translation files from translation source files. You can run it using the following command:

    dart run slang

    By default, running slang without arguments triggers the generate mode, which scans your translation files and builds the corresponding Dart files. This is typically faster than using build_runner.

  9. Install slang_build_runner

    main

    To use slang with build_runner (useful for combining multiple code generators), add slang to your dependencies and both build_runner and slang_build_runner to your dev_dependencies in pubspec.yaml.

    # pubspec.yaml
    dependencies:
      slang: <version>
    
    dev_dependencies:
      build_runner: <version>
      slang_build_runner: <version>