Lokalise Flutter SDK

The lokalise_flutter_sdk package provides support for over-the-air translation updates from lokalise.com.

🚧 Lokalise Flutter SDK is in beta!

We really appreciate your feedback so please reach out to our Support team through the chat widget at lokalise.com (bottom right corner) if you have questions or suggestions for improvements.

Copies of these instructions are available:

Features

  • .arb to .dart file processor, inspired and following the footsteps of flutter gen-l10n.
  • Custom localization class based on AppLocalizations by flutter gen-l10n for seamless replacement.
  • Over-The-Air functionality to deliver your text updates faster.

πŸ“˜ Note on .arb file management

This SDK does not cover managing (downloading and uploading) the .arb files. For that, we recommend Lokalise CLIv2.

Getting started

You need to have a working Flutter project. To get started with Flutter Internalization check the official documentation.

Enabling Over-The-Air functionality in your project requires the following actions:

  • Prepare your Lokalise project.
  • Prepare your Flutter project.
  • Integrate the SDK into your application.

Preparing your Lokalise project

Assuming you already have a Lokalise project, there are only a couple of steps to perform:

Preparing your Flutter project

1. Update pubspec.yaml

Add the intl and lokalise_flutter_sdk packages to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:        # Add this line
    sdk: flutter                # Add this line   
  intl: ^0.17.0                 # Add this line 
  lokalise_flutter_sdk: ^0.5.1  # Add this line

2. Add .arb files to the lib/l10n/ directory

Add the ARB files to the lib/l10n/ directory of your Flutter project. We recommend that you download them from Lokalise.

On the Download section, select the Flutter (.arb) format, enable the File structure -> One file per language. Bundle structure: option, and set the value to intl_%LANG_ISO%.%FORMAT%.

Flutter is considered to be an Other platform on Lokalise. Therefore, assign your keys appropriately to the Other platform (learn how).

πŸ“˜ Example .arb file for test purposes

For testing purposes, you can manually add an intl_en.arb file. For example:

{
   "@@locale": "en",
   "helloWorld": "Hello World!",
   "@helloWorld": {
     "description": "The conventional newborn programmer greeting"
   },
   "title": "Yes, this is a title!"
}

Add one ARB file for each locale you need to support in your Flutter app. Name them using the following pattern: intl_LOCALE.arb. Here's an example of > an intl_es.arb file:

{
   "helloWorld": "Β‘Hola Mundo!"
}

3. Set up the project and generate .dart files

Install dependencies by running:

flutter pub get

Generate the .dart files from the provided .arb files:

flutter pub run lokalise_flutter_sdk:gen-lok-l10n

You should see the generated files in the lib/generated/ directory.

Integrating the SDK in your app

The package provides Lokalise and Lt classes. Lt class is generated and the name is customizable.

Lt is used to:

  • Configure the localization in the app using Lt.delegate, Lt.supportedLocales and Lt.localizationsDelegates parameters.
  • Retrieve the translations using Lt.of(context) calls.

Lokalise is used to:

  • Configure a Lokalise project to use with the help of Lokalise.init method.
  • Retrieve the latest translations using the Lokalise.instance.update() method.

1. Import all necessary packages and classes

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lokalise_flutter_sdk/ota/lokalise_sdk.dart';
import 'generated/l10n.dart';

2. Configure the Lokalise project in the main function

void main() async { // Due to some implementation details, we require the `main` function to be `async`.
    WidgetsFlutterBinding.ensureInitialized();
    await Lokalise.init(
        sdkToken: 'Lokalise SDK Token', // Make sure that `sdkToken` is an SDK token (not an API token or JWT).
        projectId: 'Project ID',
        preRelease: true, // Add this only if you want to use prereleases. Use Bundle freeze functionality in production
    );
    runApp(const MyApp());
}

3. Configure localization in the app widget

class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Lokalise SDK',
            theme: ThemeData(
                primarySwatch: Colors.blue,
            ),
            home: const MyHomePage(),
            // You can use  Lt.localizationsDelegates for shorter declaration of localizationsDelegates
            localizationsDelegates: const [ 
              Lt.delegate, // This adds Lt to the delegate call stack
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: Lt.supportedLocales, // This lists supported locales based on available languages in generated `.dart` files
        );
    }
}

4. Add the Lokalise.instance.update() call to your initial page

class MyHomePage extends StatefulWidget {
    const MyHomePage({Key? key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  bool _isLoading = true;

    @override
    void initState() {
        super.initState();
        Lokalise.instance.update().then( // This is an async call, handle it appropriately
            (response) => setState(() {
              _isLoading = false; 
            }),
            onError: (error) => setState(() { _isLoading = false; })
        );
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text(Lt.of(context).title),
            ),
            body: Center(
              child: _isLoading 
                ? const CircularProgressIndicator() 
                : Center(
                    child: Text(Lt.of(context).helloWorld),
            )),
        );
    }
}

Final product

Here's a full example that demonstrates the usage of the Flutter SDK (important lines are marked with comments):

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lokalise_flutter_sdk/ota/lokalise_sdk.dart'; // Imports the SDK
import 'generated/l10n.dart'; // Imports the generated Lt class

void main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Lokalise.init( // Configures the SDK
        sdkToken: 'Lokalise SDK Token',
        projectId: 'Project ID',
        preRelease: true, // Add this only if you want to use prereleases. Use Bundle freeze functionality in production
    );
    runApp(const MyApp());
}

class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Lokalise SDK',
            theme: ThemeData(
                primarySwatch: Colors.blue,
            ),
            home: const MyHomePage(),
            localizationsDelegates: const [
              Lt.delegate, // This adds Lt to the delegate call stack
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: Lt.supportedLocales, // Provides a list of supported locales
        );
    }
}

class MyHomePage extends StatefulWidget {
    const MyHomePage({Key? key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  bool _isLoading = true;

    @override
    void initState() {
        super.initState();
        Lokalise.instance.update().then( // Ensures application has the latest translations
            (response) => setState(() {
              _isLoading = false; 
            }),
            onError: (error) => setState(() { _isLoading = false; })
        );
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text(Lt.of(context).title), // Gets the translation
            ),
            body: Center(
              child: _isLoading 
                ? const CircularProgressIndicator() 
                : Center(
                    child: Text(Lt.of(context).helloWorld), // Gets the translation
            )),
        );
    }
}

πŸ“˜ Note on updating local translations

After the translations have been changed (lib/l10n/intl_LOCALE.arb), use the flutter pub run lokalise_flutter_sdk:gen-lok-l10n command to regenerate the Dart classes.

Additional details

Bundle freeze

To use the bundle freeze functionality, the SDK uses the version key from the pubspec.yaml located in the Flutter project root.

Given a version: 1.2.3+4 value, 1.2.3 is extracted and passed to the OTA server.

Customization

You can customize the Dart class name generated by the gen-lok-l10n command by adding a lok-l10n.yaml file in lib/l10n like this:

output-class: 'MyCustomClassName'

Limitations and known issues

  • Localizations.override widget is not supported.
    • No crashing occurs, but unexpected things might happen.
  • SDK supports plural translations that contain only plurals without any extra text.
    • {count, plural, =0{no apples} =1{one apple} other{{count} apples}} is supported.
    • I got {count, plural, =0{no apples} =1{one apple} other{{count} apples}} is not supported.
    • Double-check the generated .dart files before committing to using a plural key.

License

This plugin is licensed under the BSD 3 Clause License.

Copyright (c) Lokalise team.

Libraries

otav2/data/api/exceptions/api_exception
otav2/data/api/clients/base_client_response
otav2/domain/models/bundle
ota/model/bundle_archive
ota/service/bundle_data
otav2/data/api/clients/bundle_downloader/bundle_downloader_client
otav2/data/api/clients/bundle_downloader/response/bundle_downloader_response
otav2/data/persistence/entities/bundle_entity
otav2/domain/exceptions/bundle_not_found_exception
otav2/data/persistence/bundle_persistence
otav2/data/api/requests/bundle_request
otav2/data/api/responses/bundle_response
ota/service/bundle_service
otav2/domain/models/credentials
otav2/data/api/clients/ota/response/success/get_bundle_response
otav2/domain/use_cases/get_local_bundle_use_case
otav2/domain/dto/get_remote_bundle_dto
otav2/domain/use_cases/get_up_to_date_bundle_use_case
otav2/domain/exceptions/internal_exception
otav2/domain/models/json_serializable
ota/model/label_tr
otav2/domain/models/language_bundle
otav2/data/repositories/local_bundle_repository
ota/service/logger
ota/exceptions/lokalise_exception
lokalise_flutter_sdk
ota/lokalise_sdk
otav2/helpers/message_parser_wrapper
otav2/data/api/ota_api
otav2/data/api/clients/ota/ota_client
otav2/data/api/clients/ota/response/error/ota_error_info
otav2/data/api/clients/ota/response/error/ota_error_response
otav2/data/api/clients/ota/response/ota_response
otav2/domain/models/translation/plural_translation
ota/service/proxy
ota/model/release_data
otav2/data/repositories/remote_bundle_repository
ota/data/sdk_ota_data
ota/service/sdk_store
otav2/domain/models/translation/simple_translation
ota/model/stored_release_data
otav2/domain/models/translation/translation
otav2/domain/models/translation/translation_element
otav2/domain/models/enums/translation_element_type
ota/model/translation_result
otav2/domain/models/enums/translation_type
ota/exceptions/update_exception
otav2/domain/models/validation_error
otav2/domain/exceptions/validation_exception