Library / SDK
slang-i18n/slang avatar
slang-i18n/slang

slang turns your JSON translations into typed Dart code

Type-safe i18n for Dart and Flutter

618 stars66 forksDartMIT

At a glance

What is it?
slang is a Dart and Flutter internationalisation package that reads JSON, YAML, CSV or ARB translation files and generates a Dart class so that a missing key or a misspelled argument is a compile error. It suits teams that want type safety in their strings, and it adds a code generation step to every translation change.
Who is it for?
Adopt slang when a Flutter or Dart project has translation files that are edited by more than one person and you want missing keys and wrong arguments caught by the compiler rather than by a tester. Skip it if your project has a handful of strings that will never need pluralisation, custom contexts or locale fallback.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 78 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The generated class is the interface to your translations

The idea is simple and worth stating plainly before the details. You keep your translations in JSON, YAML, CSV or ARB files, you run a generator, and you get a Dart file containing a class whose members mirror the structure of your locale files. From then on the translations are ordinary Dart method calls with ordinary Dart types. The example in the README shows the whole surface at once:

dart
final t = Translations.of(context); // there is also a static getter without context

String a = t.mainScreen.title;                         // simple use case
String b = t.game.end.highscore(score: 32.6);          // with parameters
String c = t.$wip('Password Forgotten');               // fast prototyping
String d = t.items(n: 2);                              // with pluralization
String e = t.greet(name: 'Tom', context: Gender.male); // with custom context
String f = t.greet(today: DateTime.now());             // with L10n
String g = t.intro.step[4];                            // with index
String h = t.error.type['WARNING'];                    // with dynamic key
String i = t['mainScreen.title'];                      // with fully dynamic key
TextSpan j = t.greet(name: TextSpan(text: 'Tom'));     // with RichText

The type safety claim comes from the fact that the generator sees your locale file and emits a member for every key, with a parameter type for every interpolation placeholder. A call that passes a String where a double is expected does not compile. A key that does not exist in any locale does not exist on the class.

The generation step is a Dart command, not a build_runner dependency

There are two ways to produce the Dart file, and the recommended one is a plain command. Built in, and recommended during development, it is:

text
dart run slang

The alternative is useful for CI and for an initial git checkout, and it requires the slang_build_runner package, in which case the command is dart run build_runner build -d. That distinction is the whole of the setup story, because the package list in the README is careful to mark build_runner as only if you use it, and slang_build_runner as only if you use build_runner. The other half of that story is the dependency set itself, where slang is the base package and slang_flutter is added if you use Flutter:

yaml
dependencies:
  slang: <version>
  slang_flutter: <version> # also add this if you use flutter

dev_dependencies:
  build_runner: <version> # ONLY if you use build_runner (1/2)
  slang_build_runner: <version> # ONLY if you use build_runner (2/2)

The library is Flutter independent, so a plain Dart project can use it, and the getting started section points at a Dart only section for that case.

The CLI does more than generate, it analyses and migrates

The command line is described as extensive, and the list of subcommands is where the project earns its keep for a working application. Six examples are given:

bash
dart run slang                               # generate dart file
dart run slang analyze                       # unused and missing translations
dart run slang normalize                     # sort translations according to base locale
dart run slang configure                     # automatically update CFBundleLocalizations
dart run slang edit move loginPage authPage  # move or rename translations
dart run slang migrate arb src.arb dest.json # migrate arb to json

The analyze command is the interesting one for a team, because it reports unused and missing translations, which is the class of bug that a generated class can catch at compile time but a stale translation set will still accumulate over time. Normalize sorts translations according to the base locale, which keeps merge diffs small when several translators touch the same file. Configure updates CFBundleLocalizations automatically, which is the iOS specific chore that otherwise gets forgotten. The migrate command handles the ARB case for teams coming from Flutter's own localisation format, and the tools section of the table of contents lists further commands for cleaning, applying, editing, checking outdated translations, statistics and an MCP server for editor integration.

Locale files are named by convention and split by namespace

The file format is deliberately boring. The name of a locale file follows the pattern of locale and extension, the most common i18n directories are assets/i18n and lib/i18n, and the file names carry the locale, with a country code variant shown as an example. A minimal pair of English and German files looks like this:

json5
// File: en.i18n.json
{
  "hello": "Hello $name",
  "save": "Save",
  "login": {
    "success": "Logged in successfully",
    "fail": "Logged in failed"
  }
}

The nesting in the file becomes nesting in the generated class, so login.success is a member and the structure of your translations is visible in the API. Large files can be split into smaller ones through namespaces, which is the structuring feature that keeps a translation file with hundreds of keys manageable. The setup claim in the README is that there is minimal setup and no configuration needed, that you create JSON files and get started, and that the default language is configurable in build.yaml if English is not the base.

Initialising the locale is two lines in main

The fourth step of getting started is a small piece of code, and there are two ways to do it. To use the device locale, the main function ensures the widget binding is initialised and then calls LocaleSettings.useDeviceLocale before running the app:

dart
void main() {
  WidgetsFlutterBinding.ensureInitialized(); // add this
  LocaleSettings.useDeviceLocale(); // and this
  runApp(MyApp());
}

The alternative is to use a specific locale rather than the device one. The generated example file the README links to is a good place to see what a realistic generated file looks like, and the generated API is the same in both cases. The point of the whole exercise is that after this initialisation, no lookup in the application code is a string key, so a rename in a locale file is a compile error at every call site rather than a runtime fallback to the base language.

Pluralisation, custom contexts and the escape hatches

The feature list in the table of contents is where the interesting design decisions live, and they are all about making the type system carry more meaning. Pluralisation is supported, so t.items(n: 2) picks the right form from a count. Custom contexts and enums are supported, so t.greet takes a context parameter with a Gender enum, which is how languages that distinguish grammatical gender are handled without a string key per combination. Typed parameters mean the generated method signature knows the type of every interpolation value, so passing the wrong type is caught. L10n support covers the case of using the standard library's locale and date formatting rather than plain interpolation. Linked translations allow one string to reference another. Region extensions handle regional variants. Interfaces generate types for structured data such as a PageData object, and modifiers are listed alongside them. The README is explicit that pluralisation and custom context are the features that require the generator to understand the semantics of the translation set, which is why they are configuration-driven rather than inferred. Two features exist purely to give you an escape hatch, and they are worth understanding because they explain the shape of the API. The first is fallback, which covers the case where a translation is missing from a locale and the base language value is used instead. The second is prototyping, exposed in the API as the $wip method, so t.$wip('Password Forgotten') gives you a working string for a key that does not exist yet, which is how you build a screen before the translations are final. That the fully dynamic access forms also exist, t['mainScreen.title'] and t.error.type['WARNING'], matters for the same reason. The cost of the type safety is that the shape of the translation data becomes part of the API, so a screen that needs a string you have not defined yet is a compile error until you add the key or reach for the prototyping path. The README describes the workflow as a balance between bug resistance and speed, and these are the places where the trade is made explicit.

The repository is a monorepo with tool packages beside the library

The top level shows more than a single package. There is a slang/ directory for the core library, a slang_flutter/ directory for the Flutter integration, a slang_build_runner/ directory for the build_runner alternative, and two more that stand out, slang_gpt/ and slang_mcp/. The MCP package corresponds to the MCP server command listed in the tools section, which puts the translation tooling inside an editor's agent loop. There is also a release.dart and a sync.dart at the root, plus a .forgejo/ directory and a .fvmrc for the Dart SDK version. The CI badge points at a Codeberg workflow rather than GitHub, and the asset links in the README resolve to codeberg.org, so the canonical home of the project has moved away from the GitHub repository you may arrive from. The package is published on pub.dev at pub.dev/packages/slang, which is where a Dart developer will actually install it from, and the licence is MIT.

Where slang is the wrong choice

Three situations argue against it. The first is a project with a small, stable set of strings. The whole mechanism is a code generation step, a build_runner dependency or a command you must remember to run, and that cost is not worth paying for twenty strings that will never need pluralisation, custom contexts or locale fallback. The second is a team with a translation workflow that is already owned by a platform. If your strings are managed in a translation management system with its own round trip into your repository, introducing a second representation of the same data, the JSON files and the generated Dart class, creates a reconciliation problem that the type safety was supposed to solve. The third is a release process that cannot tolerate generated code in the repository. The alternative worth naming is Flutter's own gen_l10n with ARB files, which is the approach slang provides a migration path away from, since the migrate arb command converts an ARB file to JSON. That path exists because the ARB format, with its metadata, is the more structured of the two. The honest comparison is that slang gives you a richer type system, more formatting options and a wider range of input formats, and charges for it in build complexity. Which side of that trade you should be on depends entirely on how many strings you have and how many people touch them.

Editorial conclusion

Adopt slang when a Flutter or Dart project has translation files that are edited by more than one person and you want missing keys and wrong arguments caught by the compiler rather than by a tester. Skip it if your project has a handful of strings that will never need pluralisation, custom contexts or locale fallback. Verify first that your translation files can live in the formats it reads, since the whole model is generate-then-compile, and remember that every change to a locale file means regenerating the Dart file before the new string is available.

Frequently asked questions

What is slang?

It is a type-safe i18n solution using JSON, YAML, CSV or ARB files for Dart and Flutter. The generator reads your locale files and produces a Dart class, so a missing translation key or a wrongly typed interpolation argument becomes a compile-time error.

How do I add slang to a Flutter project?

Add the slang dependency and, if you use Flutter, the slang_flutter dependency, plus build_runner and slang_build_runner only if you use that route. Then create JSON files named by locale in assets/i18n or lib/i18n, run dart run slang to generate the Dart file, and call LocaleSettings.useDeviceLocale in main.

What does the slang CLI do besides generating code?

The subcommands include analyze for unused and missing translations, normalize to sort translations according to the base locale, configure to update CFBundleLocalizations automatically, edit move to move or rename translations, and migrate arb to convert an ARB file to JSON.

Can slang be used without Flutter?

Yes. The library is described as Flutter-independent and usable in any Dart project, with a dedicated Dart only section in the documentation. The slang_flutter package is the part you add only when you use Flutter.

Is slang still maintained?

The repository is not archived and the last push was on 2026-08-06, with releases v4.12.1 from 2026-01-31, v4.12.0 from 2026-01-18 and v4.11.0 from 2025-11-22. The licence is MIT, the package is published on pub.dev, and the project also ships slang_build_runner, slang_flutter, slang_gpt and slang_mcp packages alongside the core library.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. slang-i18n/slang on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/slang-i18n-slang.svg)](https://hysenlabs.com/projects/slang-i18n-slang)

Was this useful?