PW Localization Flutter

License

A powerful and flexible localization package for Flutter applications, designed as a replacement for EasyLocalization with enhanced features including runtime ICU formatting support and dynamic language file downloading.

Features

  • πŸš€ Easy Integration: Drop-in replacement for EasyLocalization with familiar API patterns
  • 🌍 ICU Message Formatting: Full support for ICU message format including plurals and gender rules
  • πŸ“± Dynamic Downloads: Download and update language files at runtime from remote servers
  • πŸ’Ύ Asset Fallback: Automatic fallback to asset-based translations when downloads are unavailable
  • πŸ”„ Hot Reload: Support for runtime locale switching and translation updates
  • πŸ—οΈ Code Generation: Generate type-safe translation keys from JSON files
  • 🎯 Flutter Integration: Seamless integration with Flutter's localization system

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  flutter_pipwave_localization: ^0.0.1+2

Example

A complete working example is available in the example/ directory. The example demonstrates:

  • Basic setup with LocalizationProvider
  • Using context.tr() for translations
  • ICU plural rules and variable substitution
  • Runtime language switching
  • Download functionality

To run the example:

cd example
flutter pub get
flutter run

Quick Start

1. Basic Setup

Wrap your app with LocalizationProvider:

import 'package:flutter/material.dart';
import 'package:flutter_pipwave_localization/flutter_pipwave_localization.dart';

void main() {
  runApp(
    LocalizationProvider(
      startLocale: const Locale('en'),
      assetPath: 'assets/translations',
      downloadedPath: 'translations',
      supportedLocales: const [
        Locale('en'),
        Locale('es'),
        Locale('fr'),
      ],
      child: const MyApp(),
    ),
  );
}

2. Using Translations

Access translations through the context extension:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(context.tr('welcome.title')),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(context.tr('welcome.message', args: {'name': 'John'})),
            Text(context.tr('items.count', args: {'count': 5})),
          ],
        ),
      ),
    );
  }
}

3. Using Translation Keys

// Access translation keys directly
Text(LocaleKeys.welcomeTitle.tr());
Text(LocaleKeys.itemsCount.trArgs({'count': 5}));

API Reference

LocalizationProvider

The main widget that provides localization to your app.

LocalizationProvider({
  required Widget child,
  required Locale startLocale,
  required String assetPath,
  required String downloadedPath,
  List<Locale> supportedLocales = const [Locale('en')],
  Key? key,
});

Parameters:

  • child: The widget tree to provide localization for
  • startLocale: The initial locale to use
  • assetPath: Path to asset-based translation files (e.g., 'assets/translations')
  • downloadedPath: Directory name for downloaded translation files
  • supportedLocales: List of supported locales

LocalizationService

The core service that handles translations and ICU formatting.

Key Methods

// Initialize the service
await LocalizationService.instance.initialize(
  assetPath: 'assets/translations',
  downloadedPath: 'translations',
  defaultLanguage: 'en',
  locale: const Locale('en'),
);

// Translate a key
String translated = LocalizationService.instance.translate(
  'welcome.message',
  args: {'name': 'John'},
);

// Check if locale is supported
bool isSupported = LocalizationService.instance.isLocaleSupported('en');

// Download language files
await LocalizationService.instance.downloadLanguageFiles(
  'https://api.example.com/translations',
  ['en', 'es', 'fr'],
);

// Clear downloaded translations
await LocalizationService.instance.clearDownloadedTranslations();

Context Extensions

Convenient extensions for accessing localization in widgets:

// Translate with arguments
String text = context.tr('welcome.message', args: {'name': 'John'});

// Get current locale
Locale? locale = context.currentLocale;

String Extensions

Extensions for direct string translation:

// Simple translation
String text = 'welcome.message'.tr();

// Translation with arguments
String text = 'items.count'.trArgs({'count': 5});

ICU Message Format Support

This package fully supports ICU message formatting for advanced pluralization and gender rules.

Plural Rules

Object-Based Plural Format

This package supports object-based plural definitions:

{
  "items_count": {
    "zero": "No items",
    "one": "One item",
    "many": "{count} items",
    "other": "{count} items"
  }
}

Usage:

Text(context.tr('items.count', args: {'count': 5}));

ICU Message Format Plural

You can also use the ICU message format directly in your translation strings:

{
  "items_count": "{count, plural, =0{No items} =1{One item} =2{Two items} few{Few items} many{Many items} other{{count} items}}"
}

Usage:

Text(context.tr('items_count', args: {'count': 5}));

The ICU format supports:

  • Exact matches: =0, =1, =2, etc.
  • Plural categories: zero, one, two, few, many, other
  • Variable interpolation: {count} within messages

Gender Rules

{
  "welcome": {
    "male": "Welcome Mr. {name}",
    "female": "Welcome Ms. {name}",
    "other": "Welcome {name}"
  }
}

Usage:

Text(context.tr('welcome', args: {'name': 'John', 'gender': 'male'}));

Complex ICU Formatting

{
  "message": "You have {count, plural, zero{no messages} one{one message} many{# messages}} from {sender}."
}

Usage:

Text(context.tr('message', args: {
  'count': 5,
  'sender': 'Alice'
}));

Code Generation

Generate type-safe translation keys from your JSON files:

dart run flutter_pipwave_ai_agent:generate assets/translations/en.json lib/src/generated/locale_keys.g.dart

The arguments are optional and default to:

  • Translation file: assets/translations/en.json
  • Output path: lib/src/generated/locale_keys.g.dart

You can also run without arguments to use the defaults:

dart run flutter_pipwave_ai_agent:generate

This generates a LocaleKeys class with static constants for all translation keys.

Advanced Usage

Custom Localization Setup

For more control, use the individual components:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        CustomLocalizationDelegate(
          locale: const Locale('en'),
          supportedLocales: const [Locale('en'), Locale('es')],
        ),
      ],
      supportedLocales: const [Locale('en'), Locale('es')],
      locale: const Locale('en'),
      home: const MyHomePage(),
    );
  }
}

Dynamic Locale Switching

// Access the localization service
final localization = LocalizationProvider.of(context);

// Switch locale
await localization.initialize(
  assetPath: 'assets/translations',
  downloadedPath: 'translations',
  defaultLanguage: 'en',
  locale: const Locale('es'),
);

Error Handling

try {
  await LocalizationService.instance.initialize(
    assetPath: 'assets/translations',
    downloadedPath: 'translations',
    defaultLanguage: 'en',
    locale: const Locale('en'),
  );
} catch (e) {
  // Handle initialization errors
  print('Localization initialization failed: $e');
}

Translation File Structure

Your translation files should be JSON files with the following structure:

{
  "welcome": {
    "title": "Welcome",
    "message": "Hello {name}!",
    "items": {
      "count": {
        "zero": "No items",
        "one": "One item",
        "many": "{count} items",
        "other": "{count} items"
      }
    }
  },
  "items_count_icu": "{count, plural, =0{No items} =1{One item} =2{Two items} few{Few items} many{Many items} other{{count} items}}",
  "common": {
    "ok": "OK",
    "cancel": "Cancel",
    "loading": "Loading..."
  }
}

You can use either:

  • Object-based plurals: Nested objects with zero, one, many, other, etc.
  • ICU message format: Direct string values with ICU plural syntax

Best Practices

  1. Key Naming: Use descriptive, hierarchical keys (e.g., welcome.title instead of welcomeTitle)
  2. Fallback Strategy: Always provide fallback translations in your default language
  3. ICU Formatting: Use ICU message format for complex pluralization and gender rules
  4. Asset Organization: Keep translation files organized in assets/translations/ directory
  5. Error Handling: Implement proper error handling for network failures when downloading translations

License

This project is licensed under the MIT License - see the LICENSE file for details.