easiest_localization 2.0.0-beta copy "easiest_localization: ^2.0.0-beta" to clipboard
easiest_localization: ^2.0.0-beta copied to clipboard

The easiest and fastest way to localize your application with type safety

Logo


Easiest localization (or el) is the ideological successor to the yalo package, focused on providing the easiest and fastest way to localize your Flutter application

Easiest localization is like easy_localization, but easier 😉

Why easiest_localization? #

  • 🚀 Easiest translation for any language
  • 🔌 Use as a source json or yaml files
  • 💾 React and persist to locale changes
  • ⚡ Supports plural, gender, nesting, RTL locales and more
  • ↩ī¸ Fallback locale keys redirection
  • ❤ī¸ Extension methods on BuildContext or, even, you can use el without context at all!
  • đŸ’ģ Code generation for localization files and keys
  • 🛡ī¸ Null safety and, which is more important - type safety! Your app just will not compile, if you missed some contents

If you are not happy with your language pack, want type-safety or incredible flexibility in naming of your strings - you just have to pay attention to easiest_localization

Getting started #

🔩 Installation #

dev_dependencies:
  easiest_localization: <last_version>

Just put yaml or json localization filed under any assets folder, which is described in your pubspec.yaml:

/assets
├── /en_intl.yaml
├── ...
├── /translations
│   ├── /en.yaml
│   └── /pt.yaml
└── ...any other structure will be acceptable

Don't forget to describe your assets in pubspec.yaml:

flutter:
  assets:
    - assets/
    # or / and
    - assets/translations/
    # or any folder, which you will use to store a translations

⚙ī¸ Configuration #

You can place it as a root key in your pubspec.yaml file to configure el very deeply. Or do not, if you want to use all the default settings. Just remember, that default pattern of localization file will be next:

/**/.*[A-Za-z]{2}(_intl)?.(ya?ml|json)
# ** - can be any folder or child folder of the folder or deeper, which is described as your asset in pubspec.yaml
# [A-Za-z]{2} - language code, like "en", "fr", "pt"
# ya?ml|json - you can store it in json or yaml files
# Acceptable default examples:
# /assets/en.yaml
# /assets/tr/pt.json
# /assets/i18n/zh_intl.json
# /assets/localizations/localization_fr.yaml
easiest_localization:
  # List of strings-patterns that will not be processed despite matching reg_exp
  # By default - empty
  excluded: []
  
  # The name of the class containing the localization content
  class_name: LocalizationMessages
  
  # Description of the generated localization package
  description: Generated localization package
  
  # The version of the easiest_localization_version package used as a dependency
  # in the generated localization package.
  #
  # May be useful if you are using easiest_localization not from pub.dev
  # By default it is the same version as in your application's pubspec.yaml
  easiest_localization_version: <installed version>
  
  # Name of the generated localization package and the folder with it
  package_name: localization
  
  # Relative path to the generated localization package
  package_path: './'
  
  # Version of the generated localization package
  package_version: 1.0.0
  
  # An example of supported file names with a default RegExp: https://regex101.com/r/CALDhV/2
  #
  # !!! A mandatory requirement for RegExp: it must have named parameter (?<lang>[a-z]{2}) !!!
  reg_exp: '(\W)(?<pattern>intl)?_?(?<lang>[a-z]{2})[_-]?(?<country>[A-Z]{2})?.(ya?ml|json)$'
  
  # The language code or full localization to be used as the content source,
  # defaulting to other languages if no fields are described.
  #
  # By default - null. This means that if one language has content for certain keys,
  # and another language does not have those keys at all - all values for those keys in
  # the other language will be empty.
  #
  # Examples: "en", "en_US", "en_CA", etc.
  primary_localization: <language code or full locale>

  # Whether to apply code formatting to the generated localization package.
  #
  # If true - generation takes 1-2 seconds longer. false by default
  format_output: false

  # Whether the merged localization files need to be saved.
  #
  # This can be useful when you have multiple files for the same language.
  # For example - en, en_CA and en_UK. In this case, the main localization file - en,
  # will contain all the content. And each of the specific files - en_CA / en_UK can contain
  # only the content that should be different from the main file.
  #
  # However, for cloud storages, each of the language files must be fulfilled -
  # this is where merged files will be useful.
  save_merged_files: false

🖨ī¸ Code generation #

One you installed el, specified assets under pubspec.yaml and have, at least, one localization file at your assets folder - you able to generate type-safe localization code. To do that just run:

dart run easiest_localization

After that you will see the generated package with the default or your own name under specified folder. Then - you should install that package to your app. By default it would be like that:

dependencies:
  localization:
    path: ./ # here should be a default path - "./" or [package_path] from the configuration

✍ī¸ How to use (example) #

After generation and installation of generated package was complete - you able to use el. To do so you just need to add few variables to your MaterialApp:

import 'package:flutter/material.dart';
import 'package:localization/localization.dart'; // <- Import generated package

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Builder(
      builder: (BuildContext context) {
        return MaterialApp(
          /// The first variable - [supportedLocales], which contains all the generated and fallback locales
          supportedLocales: supportedLocales,

          /// The second - [localizationsDelegates], which contains generated and default delegates for work of localizations in general
          localizationsDelegates: localizationsDelegates,

          /// Access to locale from the context               âŦ‡ ī¸Ž
          onGenerateTitle: (BuildContext context) => context.el.title,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: Builder(
            /// Or just by using a getter [el]                    âŦ‡ ī¸Ž
            builder: (BuildContext context) => MyHomePage(title: el.intro),
          ),
        );
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              /// Equals to el.pages.home.counter(_counter)
              el.getContent<Pages>('pages').getContent<PagesHome>('home').getContent('counter')(_counter),
            ),
            Text('greetings2'.tr()(username: 'Alex')),
            Text(tr('greetings3.home')(username: 'Alex')),
            Text(context.tr('intro')),
          ]
              .map(
                (Widget child) => Padding(
                  padding: const EdgeInsets.only(bottom: 12),
                  child: child,
                ),
              )
              .toList(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,

        /// Equals to el.pages.home.incrementButton.title
        tooltip: el['pages']['home']['incrementButton']['title'],
        child: const Icon(Icons.add),
      ),
    );
  }
}

🛠ī¸ Methods of using #

As you saw above - el brings a lot of methods to retrieve localization content. They all splits into two groups: type safe and not. The first group - is recommended to use and the second - only if it is really necessary or if you want to retrieve localizations dynamically (by combining variables, for example), or...if you want to change the translation library at your app

🛡 Type safe

/// Simple value
Messages.of(context).title; // Easiest localization app
context.el.title;
Messages.el.title;
el.title;

/// Pluralized value
Messages.of(context).cow(5); // There are 5 cows
context.el.cow(5);
Messages.el.cow(5);
el.cow(5);

/// Gender value
el.usernameHello(Gender.male); // Hello, mister!
el.usernameHello(Gender.female); // Hello, madam!
el.usernameHello(Gender.other); // Hello!

/// Namespaced value
Messages.of(context).views.home.description; // Home - is the main screen of the app
context.el.views.home.description;
Messages.el.views.home.description;
el.views.home.description;

/// Value with arguments (fully type-safe with IntelliSense suggestions from the IDE)
Messages.of(context).views.intro.greetings(username: 'Jack'); // Hello, Jack!
context.el.views.intro.greetings(username: 'Jack');
Messages.el.views.intro.greetings(username: 'Jack');
el.views.intro.greetings(username: 'Jack');

/// With arguments and pluralization and namespaces (also - fully type-safe)
el.views.settings.deleteAccount(14, username: 'Jack'); // Jack, your account will be deleted in 14 days.

đŸĻ† Dynamic

From string

/// Simple value
'title'.el; // Easiest localization app
'title'.tr();
tr('title');
context.tr('title');

/// Pluralized value
'cow'.el(5); // There are 5 cows
'title'.tr(5);
tr('title')(5);
context.tr('title')(5);

/// Gender value
'usernameHello'.el(Gender.male); // Hello, mister!
'usernameHello'.el(Gender.female); // Hello, madam!
'usernameHello'.el(Gender.other); // Hello!

/// Namespaced value
'views.home.description'.el; // Home - is the main screen of the app
'views.home.description'.tr();
tr('views.home.description');
context.tr('views.home.description');

/// Value with arguments
'views.intro.greetings'.el(username: 'Jack');
'views.intro.greetings'.tr()(username: 'Jack');
tr('views.intro.greetings')(username: 'Jack');
context.tr('views.intro.greetings')(username: 'Jack');

From el

/// Simple value
el['title']; // Easiest localization app
el.getContent<String>('title');

/// Pluralized value
el['cow'](5); // There are 5 cows
el.getContent('cow')(5);

/// Gender value
el['usernameHello'](Gender.male); // Hello, mister!
el['usernameHello'](Gender.female); // Hello, madam!
el['usernameHello'](Gender.other); // Hello!

/// Namespaced value
el['views']['home']['description']; // Home - is the main screen of the app
el.getContent<Views>('views').getContent<ViewsHome>('home').getContent<String>('description');

/// Value with arguments
el['views']['intro']['greetings'](username: 'Jack');
el.getContent<Views>('views').getContent<ViewsIntro>('intro').getContent<Function>('greetings')(username: 'Jack');

📜 Localization content #

For now you can use as a source only local yaml or json files. No remotes. Json example will be simpler, because of that let's take a look on a yaml localization file:

# simple string
title: Easiest Localization App

# string with description
intro:
  value: This is a intro screen title
  desc: For some reason we decided to use exactly that title for that screen

# pluralized
product: &product
  zero: There are ${howMany} products
  one: There are ${howMany} product
  two: There are ${howMany} products
  few: There are ${howMany} products
  many: There are ${howMany} products
  other: There are ${howMany} products
  desc: How many products do we have?

# gender
bookAfterwords:
  male: Thank you for reading, mr. ${username}!
  female: Thank you for reading, ms. ${username}!
  other: Thank you for reading, dear ${username}!
  desc: What the user will see, after he read the book

# namespace
pages:
  home:
    title: Home
    description: Here you can see the main content
    counter:
      one: "You have pushed the button ${howMany} many time"
      other: "You have pushed the button ${howMany} many times"
    incrementButton:
      title: Increment
  settings:
    title: Settings
    description: Here you can change your settings
  profile:
    title: Profile
    description: Here you can see your personal info and change it
  product:
    title: *product

# arguments
greetings: Hello, ${username}!

greetings2:
  value: Hello, dear ${username}!
  desc: This is greetings with an argument [username]

greetings3:
  home: Hello, ${username} at home page!
  settings: Hello, ${username} at settings page!
  custom: Hello, ${username} at ${page} page!

aboutCows:
  one: Maybe there are ${howMany} cow? What do you think, ${username}?
  other: Maybe there are ${howMany} cows? What do you think, ${username}?

Nesting #

You able to use all power of the yaml approach - anchors, clear and straightforward syntax, multiline strings and so on. Also, el allows you to nest variables each in other to have a namespaced zones. For example, you can split your localization content in the same way as it splitted in code (by domains). Like, you have some screens - then you can define variables of every screen under corresponding screen name and they all will be under their main parent - screens namespace:

screens:
  home:
    title: abc
    description: def
    somethingElse: ghi
  settings:
    title: bcd
    description: efg
    somethingElse: hij
  # etc.

Pluralization #

You, of course, able to pluralize the content. See an example above, near the comment "pluralized". And also, you able to have any arguments (any String arguments) at your localization content. To make content pluralized, you should specify at least two keys:

  • one
  • other

All another arguments from that type of content are optional

product:
  zero: There are ${howMany} products
  one: There are ${howMany} product
  two: There are ${howMany} products
  few: There are ${howMany} products
  many: There are ${howMany} products
  other: There are ${howMany} products
  desc: How many products do we have?

Arguments #

If you specify arguments inside localization strings (it doesn't matter where exactly this string is located - in a pluralization block, gender definition or in a simple string), the corresponding function will be generated instead of the usual variable. And you will have to pass the corresponding named argument to this function. For example:

someKey: Hello, ${username}! What do you want to do ${day}? Will you go with me and ${friend} to the celebration? 

will generate a code, which be able to use as a function with next signature:

final String Function({required String username, required String day, required String friend}) someKey;

You can embed arguments to any type of content - described, pluralized and nested (inside namespaces). But there are some restrictions about argument names:

  1. An argument named howMany will always be of type int if it is inside a pluralization block (specified inside one of the following keys: zero, one, two, few, many, other)
  2. The argument called precision will always be of type double if it is inside a pluralization block (see above)
  3. An argument called gender will always be of type Gender if it is specified inside the sex definition block (specified inside one of the following keys: female, male, other)

⭐ What next? #

  • ✅ Write tests for most critical part of the logic
  • ✅ Write more additional tests
  • ✅ Release 1.0.0
  • ❎ Add support of dynamically retrieving of content from the remote source (with full-type safety)
  • ❎ Release 2.0.0
  • ❎ Something else?

If you want additional features - join to maintainers. Let's code together!

10
likes
0
points
1.14k
downloads

Publisher

verified publisheralphamikle.dev

Weekly Downloads

The easiest and fastest way to localize your application with type safety

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

dart_style, flutter, flutter_localizations, intl, merger, path, yaml

More

Packages that depend on easiest_localization