easiest_localization 1.0.3 copy "easiest_localization: ^1.0.3" to clipboard
easiest_localization: ^1.0.3 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:
  # Default value
  namespace: intl
  # Default value
  language_prefix: "[A-Za-z]{2}"
  # By default - is empty / null
  excluded: []
  # Default name of output class
  class_name: LocalizationMessages
  # Default description of generated package
  description: Some description for generated package
  # Default name of generated package
  package_name: localization
  # Default path to generated package - the root of your project, under which will be created folder <package_name> (localization - by default)
  package_path: ./
  # Default version of generated package
  package_version: 1.0.0
  # Instead of using <namespace> and <language_prefix> params, you can define your own reg_exp to help el determine your localization files
  # Only one requirement applies to that regexp - it should contain at least one named argument <lang>
  # Like ".*translation.*(?<lang>.{2})" - will match on any json / yaml file under any folder with substring "translation" or if file will contain that substring
  reg_exp: "(?<lang>[A-Za-z]{2})_(?<pattern>intl).(ya?ml|json)$"
  # Will run "dart fix --apply" to generated package to make it perfect, but consumes a lot of time (100ms - without and about 5-7 seconds with it)
  format_output: false
  # The map of fallback languages. It works on next way:
  # fallback_locales:
  #   <missed_language>: <existed_fallback_language>
  # Wildcard '*" means, that existed fallback will be such for any missed locale
  # If you will not specify that parameter, then - the first founded locale will be used instead of missed
  fallback_locales:
    fr: en # for french locale will be used english
    it: pt # for italian locale will be user portuguese
    '*': zh # for all another missed locales will be used chinese

🖨ī¸ 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 the default or your <package_path>

✍ī¸ 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!

0
likes
115
pub points
18%
popularity

Publisher

verified publisheralphamikle.dev

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

Repository (GitHub)
View/report issues

Documentation

API reference

License

CC-BY-4.0 (LICENSE)

Dependencies

dart_style, flutter, flutter_localizations, intl, path, yaml

More

Packages that depend on easiest_localization