Skip to content

Migration From layrz_models

Kenny Mochizuki Escalona edited this page Aug 16, 2026 · 1 revision

Migrating from layrz_models to layrz_i18n

The i18n engine has been extracted from layrz_models into its own standalone package, layrz_i18n. This guide covers everything you need to know to update your app.

Why Migrate?

Previously, if your app needed translations, you had to depend on layrz_models, which pulled in a large dependency tree:

  • dio (HTTP client)
  • web_socket_channel (WebSocket support)
  • latlong2 (geographic coordinates)
  • layrz_icons (icon package)
  • recase (string transformation utilities)
  • layrz_logging (logging)

layrz_i18n is completely standalone. It depends only on:

  • package:flutter/widgets.dart (the Flutter SDK)
  • freezed_annotation, json_annotation (code generation)
  • collection, web (small utilities)

If your app only needs translations, you can now skip layrz_models entirely and use layrz_i18n directly, resulting in a smaller app bundle and faster dependency resolution.

Dependency and Import Changes

Add layrz_i18n

Update pubspec.yaml:

dependencies:
  layrz_i18n: ^1.0.0

Update imports

Before:

import 'package:layrz_models/layrz_models.dart';

After:

import 'package:layrz_i18n/layrz_i18n.dart';

Keep or Remove layrz_models?

  • If your app uses other parts of layrz_models (models, API clients, etc.), keep it in pubspec.yaml.
  • If you only used it for i18n, you can remove it.

Important: Both packages can coexist during migration, because layrz_models still ships its own copy of the i18n engine. However, you must wire only one localizations delegate into your WidgetsApp or MaterialApp:

localizationsDelegates: [
  LayrzI18n.delegate(
    languages: languages,
    supportedLocales: supportedLocales,
    fallbackLocale: const Locale('en'),
  ),
  // NOT both LayrzI18nDelegate and LayrzAppLocalizationsDelegate
],

Renamed Symbols

The following symbols have been renamed. Use find-and-replace to update your code, being careful to replace longer names first:

Old (layrz_models) New (layrz_i18n)
LayrzAppLocalizations LayrzI18n
LayrzAppLocalizationsDelegate LayrzI18nDelegate
debugCheckHasLayrzAppLocalizations debugCheckHasLayrzI18n

Example find-and-replace commands (in order of longest first):

# In your project root, replace all occurrences:
sed -i 's/debugCheckHasLayrzAppLocalizations/debugCheckHasLayrzI18n/g' lib/**/*.dart
sed -i 's/LayrzAppLocalizationsDelegate/LayrzI18nDelegate/g' lib/**/*.dart
sed -i 's/LayrzAppLocalizations/LayrzI18n/g' lib/**/*.dart

Method and extension names stay the same

These names have not changed and require no updates:

  • t(), tc(), te(), tce() — translation methods
  • hasTranslation() — check if a key exists
  • of(), maybeOf() — retrieve the instance from context
  • getClosestLocale() — locale matching
  • setDeveloperMode() — debug mode control
  • detectedLocale — system locale property
  • delegate() — LocalizationsDelegate factory
  • load() — internal loader
  • context.i18n, context.maybeI18n — context extensions

Changed Signature: te()

The te() method no longer accepts an EdgeInsets padding parameter:

Before:

i18n.te(
  'key',
  style: TextStyle(fontSize: 16),
  padding: EdgeInsets.all(4), // ← This parameter is gone
)

After:

i18n.te(
  'key',
  style: TextStyle(fontSize: 16),
)

The padding parameter was declared but never used in the original engine, so removing it has no functional impact. If you were using it, simply delete the argument.

What Did NOT Move — Still in layrz_models

These classes and methods remain exclusively in layrz_models and are not available in layrz_i18n:

  • I18nKey — API model for translation keys
  • I18nKeyInput — input payload for upserting keys
  • I18nTranslation — API model for translations
  • I18nTranslationInput — input payload for translations
  • Language — API model for languages
  • LanguageInput — input payload for languages
  • I18nKeyHistory — revision history model
  • AvailableLanguage.fragment — GraphQL fragment
  • AvailableLanguage.fetchAll() — GraphQL query to load languages from the Layrz API

The Most Important: AvailableLanguage.fetchAll()

The biggest breaking change is likely to be AvailableLanguage.fetchAll(). If your app loads languages from the Layrz API at startup, you must choose one of two approaches:

Option 1: Keep depending on layrz_models (simplest)

If you already depend on layrz_models for other reasons, continue using fetchAll():

import 'package:layrz_models/layrz_models.dart';
import 'package:layrz_i18n/layrz_i18n.dart';

final languages = await AvailableLanguage.fetchAll(uri: Uri.parse('https://api.example.com'));

runApp(MyApp(languages: languages));

Then pass the loaded languages into the LayrzI18n.delegate():

class MyApp extends StatelessWidget {
  final List<AvailableLanguage?> languages;
  const MyApp({required this.languages});

  @override
  Widget build(BuildContext context) {
    final supportedLocales = languages
      .whereType<AvailableLanguage>()
      .map((l) => l.getLocale())
      .toList();

    return WidgetsApp(
      locale: LayrzI18n.getClosestLocale(
        supportedLocales: supportedLocales,
        fallbackLocale: const Locale('en'),
      ),
      localizationsDelegates: [
        LayrzI18n.delegate(
          languages: languages,
          supportedLocales: supportedLocales,
          fallbackLocale: const Locale('en'),
        ),
      ],
      supportedLocales: supportedLocales,
      builder: (context, child) => ...,
    );
  }
}

Option 2: Load languages yourself (if removing layrz_models)

Write your own language loader. The AvailableLanguage in layrz_i18n has a fromJson() constructor, so you can deserialize from any HTTP client:

import 'package:layrz_i18n/layrz_i18n.dart';

Future<List<AvailableLanguage>> loadLanguages(Uri uri) async {
  // Use any HTTP client you prefer (http, dio, etc.)
  final response = await http.get(uri);
  
  if (response.statusCode != 200) {
    return [];
  }

  final json = jsonDecode(response.body) as Map<String, dynamic>;
  final languagesList = json['result'] as List<dynamic>;

  return languagesList
    .map((item) => AvailableLanguage.fromJson(item))
    .toList();
}

Then use it at startup:

void main() async {
  final languages = await loadLanguages(Uri.parse('https://api.example.com/languages'));
  runApp(MyApp(languages: languages));
}

Behaviour Differences to Be Aware Of

The following changes are intentional and documented in the 1.0.0 changelog. If your app relied on the old behaviour, you may need to adjust:

1. tc() now splits plural forms BEFORE interpolating

Old behavior (buggy): interpolate first, then split:

message: "You have {count} | You have {count} items"
tc('key', 5, {'count': 'apple | fruit'})  → "You have apple | fruit items"  (WRONG)

New behavior (correct): split first, then interpolate:

tc('key', 5, {'count': 'apple | fruit'}) → "You have apple | fruit items"  (CORRECT)

If your translations or argument values contain ' | ', behavior will now be correct. If code relied on the old broken order, update your translations to not include ' | ' in argument values.

2. shouldReload() now compares by value instead of always returning true

Old behavior: the delegate always returned true, forcing a rebuild even if nothing changed.

New behavior: compares languages, supportedLocales, and fallbackLocale by value; returns false if all are unchanged.

This is a performance improvement. If your app unknowingly depended on the forced reload, you may see fewer rebuilds (the correct behavior).

3. Locale parsing fix

Old behavior:

Locale loc = LayrzAppLocalizations.getClosestLocale(
  prevLanguage: 'en-',
  supportedLocales: [Locale('en')],
  fallbackLocale: Locale('en'),
);
// Result: Locale('en', '') — INVALID (empty region code)

New behavior:

parseLocale('en-') → Locale('en')  // VALID

Malformed locale codes like 'en-' or 'en_' now produce valid Locale objects. If you were working around this bug, you can remove the workaround.

4. AvailableLanguage.messages is returned as-is

Old behavior (with makeCollectionsUnmodifiable: true): returned wrapped in EqualUnmodifiableMapView, which added allocations on every access.

New behavior (with makeCollectionsUnmodifiable: false): returned as a plain Map<String, String>.

Important: do not mutate the returned map. It is shared across all uses of that language. If you need to modify it, make a copy:

// ✓ OK: read-only usage
final msg = availableLanguage.messages['greeting'];

// ✗ WRONG: don't mutate
availableLanguage.messages['greeting'] = 'Bonjour!';

// ✓ OK: make a copy if you need to modify
final mutableMessages = Map<String, String>.from(availableLanguage.messages);
mutableMessages['greeting'] = 'Bonjour!';

New Features

layrz_i18n introduces two new conveniences:

Context Extension: context.i18n and context.maybeI18n

Instead of LayrzI18n.of(context).t('key'), you can now write:

context.i18n.t('key')  // asserts if LayrzI18n is not available
context.maybeI18n?.t('key')  // returns null if not available

See Context Extension for details.

Exported parseLocale() helper

The parseLocale(String? code) function is now exported and can be used standalone:

import 'package:layrz_i18n/layrz_i18n.dart';

final locale = parseLocale('en-US');  // Locale('en', 'US')

See Locale Detection for the full reference.

Migration Checklist

  1. Add dependency — Add layrz_i18n: ^1.0.0 to pubspec.yaml and run flutter pub get.

  2. Update imports — Replace package:layrz_models with package:layrz_i18n in all i18n-related files.

  3. Rename symbols — Replace the three renamed classes:

    • debugCheckHasLayrzAppLocalizationsdebugCheckHasLayrzI18n
    • LayrzAppLocalizationsDelegateLayrzI18nDelegate
    • LayrzAppLocalizationsLayrzI18n
  4. Remove te() padding — Delete any padding: ... arguments from te() or tce() calls.

  5. Handle language loading — If you use AvailableLanguage.fetchAll(), choose Option 1 (keep layrz_models) or Option 2 (write your own loader).

  6. Test thoroughly — Pay special attention to:

    • Plural forms (tc(), tce())
    • Argument values containing ' | '
    • Locale detection and fallback behavior
    • Rich text rendering (te(), tce())
  7. Clean up — If you no longer need layrz_models, remove it from pubspec.yaml and delete unused imports.

Questions?

Refer to the full layrz_i18n documentation for more details on any topic.

Clone this wiki locally