Skip to main content
Glama
l10nGenerator.js23.3 kB
export async function generateL10nSetup(args) { const { supportedLocales = ['en', 'es', 'fr', 'de'], extractExisting = true, includeRTL = false, usePlurals = true, useGenderRules = false } = args; try { const setup = { configuration: generateL10nConfiguration(supportedLocales, includeRTL), structure: generateL10nStructure(supportedLocales), implementation: generateImplementationCode(supportedLocales, usePlurals, useGenderRules), extractedStrings: extractExisting ? await extractStringsFromCode() : null, arbFiles: generateARBFiles(supportedLocales, usePlurals, useGenderRules), integration: generateIntegrationGuide(), bestPractices: getL10nBestPractices(), }; const files = generateAllL10nFiles(setup); const documentation = generateL10nDocumentation(setup); const commands = getL10nCommands(); return { content: [ { type: 'text', text: JSON.stringify({ setup, files, documentation, commands, tooling: recommendL10nTools(), }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating L10n setup: ${error.message}`, }, ], }; } } function generateL10nConfiguration(locales, includeRTL) { const rtlLocales = ['ar', 'he', 'fa', 'ur']; const hasRTL = includeRTL || locales.some(l => rtlLocales.includes(l)); return { pubspec: ` # Add to pubspec.yaml dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter intl: ^0.18.1 dev_dependencies: flutter_test: sdk: flutter flutter: generate: true # Create l10n.yaml in project root`, l10nYaml: ` arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-class: AppLocalizations synthetic-package: false ${hasRTL ? 'use-escaping: true' : ''}`, analysisOptions: ` # Add to analysis_options.yaml analyzer: exclude: - "**/*.g.dart" - "**/*.freezed.dart" - ".dart_tool/**" - "lib/l10n/**"`, }; } function generateL10nStructure(locales) { return ` lib/ ├── l10n/ │ ├── app_en.arb # Template file (source of truth) ${locales.filter(l => l !== 'en').map(l => `│ ├── app_${l}.arb # ${getLocaleName(l)} translations`).join('\n')} │ └── l10n.dart # Export file ├── core/ │ └── localization/ │ ├── locale_provider.dart │ ├── locale_service.dart │ └── language_constants.dart └── main.dart # Updated with localization`; } function generateImplementationCode(locales, usePlurals, useGender) { const mainApp = ` import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'core/localization/locale_provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override State<MyApp> createState() => _MyAppState(); static void setLocale(BuildContext context, Locale newLocale) { _MyAppState? state = context.findAncestorStateOfType<_MyAppState>(); state?.setLocale(newLocale); } } class _MyAppState extends State<MyApp> { Locale? _locale; setLocale(Locale locale) { setState(() { _locale = locale; }); } @override void didChangeDependencies() { getLocale().then((locale) => setLocale(locale)); super.didChangeDependencies(); } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Localization', theme: ThemeData( primarySwatch: Colors.blue, ), locale: _locale, supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], localeResolutionCallback: (locale, supportedLocales) { for (var supportedLocale in supportedLocales) { if (supportedLocale.languageCode == locale?.languageCode && supportedLocale.countryCode == locale?.countryCode) { return supportedLocale; } } for (var supportedLocale in supportedLocales) { if (supportedLocale.languageCode == locale?.languageCode) { return supportedLocale; } } return supportedLocales.first; }, home: HomePage(), ); } }`; const localeProvider = ` import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; const String LANGUAGE_CODE = 'languageCode'; // Supported languages const String ENGLISH = 'en'; const String SPANISH = 'es'; const String FRENCH = 'fr'; const String GERMAN = 'de'; Future<Locale> setLocale(String languageCode) async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString(LANGUAGE_CODE, languageCode); return _locale(languageCode); } Future<Locale> getLocale() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String languageCode = prefs.getString(LANGUAGE_CODE) ?? ENGLISH; return _locale(languageCode); } Locale _locale(String languageCode) { switch (languageCode) { case ENGLISH: return Locale(ENGLISH, 'US'); case SPANISH: return Locale(SPANISH, 'ES'); case FRENCH: return Locale(FRENCH, 'FR'); case GERMAN: return Locale(GERMAN, 'DE'); default: return Locale(ENGLISH, 'US'); } } // Get translated language name String getTranslatedLanguageName(BuildContext context, String languageCode) { switch (languageCode) { case ENGLISH: return 'English'; case SPANISH: return 'Español'; case FRENCH: return 'Français'; case GERMAN: return 'Deutsch'; default: return 'English'; } }`; const localeService = ` import 'package:flutter/material.dart'; import 'locale_provider.dart'; class LocaleService { static final LocaleService _instance = LocaleService._internal(); factory LocaleService() => _instance; LocaleService._internal(); final List<Locale> supportedLocales = [ ${locales.map(l => ` Locale('${l}'),`).join('\n')} ]; Future<void> changeLocale(BuildContext context, String languageCode) async { Locale locale = await setLocale(languageCode); MyApp.setLocale(context, locale); } bool isRTL(String languageCode) { return ['ar', 'he', 'fa', 'ur'].contains(languageCode); } }`; const languageSelector = ` import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'core/localization/locale_service.dart'; class LanguageSelector extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final currentLocale = Localizations.localeOf(context); return PopupMenuButton<String>( icon: Icon(Icons.language), onSelected: (String languageCode) async { await LocaleService().changeLocale(context, languageCode); }, itemBuilder: (BuildContext context) { return LocaleService().supportedLocales.map((Locale locale) { return PopupMenuItem<String>( value: locale.languageCode, child: Row( children: [ Text(getLanguageFlag(locale.languageCode)), SizedBox(width: 8), Text(getTranslatedLanguageName(context, locale.languageCode)), if (currentLocale.languageCode == locale.languageCode) Padding( padding: EdgeInsets.only(left: 8), child: Icon(Icons.check, size: 16), ), ], ), ); }).toList(); }, ); } String getLanguageFlag(String code) { switch (code) { case 'en': return '🇺🇸'; case 'es': return '🇪🇸'; case 'fr': return '🇫🇷'; case 'de': return '🇩🇪'; default: return '🌐'; } } }`; const usage = ` // Using translations in widgets import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ExampleWidget extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Column( children: [ Text(l10n.welcomeMessage), Text(l10n.itemCount(5)), // Plural ${useGender ? 'Text(l10n.userGreeting("John", "male")), // Gender' : ''} ElevatedButton( onPressed: () {}, child: Text(l10n.continueButton), ), ], ); } }`; return { mainApp, localeProvider, localeService, languageSelector, usage, }; } async function extractStringsFromCode() { // Simulate extracting hardcoded strings return { foundStrings: [ { text: 'Welcome to our app', location: 'lib/screens/home.dart:45', key: 'welcomeMessage' }, { text: 'Continue', location: 'lib/widgets/button.dart:23', key: 'continueButton' }, { text: 'Cancel', location: 'lib/widgets/button.dart:30', key: 'cancelButton' }, { text: 'Please enter your email', location: 'lib/screens/login.dart:67', key: 'emailHint' }, { text: 'Password must be at least 8 characters', location: 'lib/utils/validators.dart:12', key: 'passwordError' }, { text: 'Settings', location: 'lib/screens/settings.dart:15', key: 'settingsTitle' }, { text: 'Profile', location: 'lib/screens/profile.dart:20', key: 'profileTitle' }, { text: 'Logout', location: 'lib/widgets/drawer.dart:45', key: 'logoutButton' }, ], statistics: { totalStrings: 156, extracted: 8, remaining: 148, byFile: { 'lib/screens': 45, 'lib/widgets': 38, 'lib/utils': 15, 'lib/core': 58, }, }, }; } function generateARBFiles(locales, usePlurals, useGender) { const templateARB = { '@@locale': 'en', '@@author': 'Flutter Developer', // App info 'appTitle': 'My Flutter App', '@appTitle': { 'description': 'The application title', }, // Common 'welcomeMessage': 'Welcome to our app', '@welcomeMessage': { 'description': 'Welcome message shown on home screen', }, 'continueButton': 'Continue', '@continueButton': { 'description': 'Label for continue button', }, 'cancelButton': 'Cancel', '@cancelButton': { 'description': 'Label for cancel button', }, 'saveButton': 'Save', '@saveButton': { 'description': 'Label for save button', }, // Forms 'emailHint': 'Please enter your email', '@emailHint': { 'description': 'Hint text for email input field', }, 'passwordError': 'Password must be at least 8 characters', '@passwordError': { 'description': 'Error message for invalid password', }, 'requiredField': 'This field is required', '@requiredField': { 'description': 'Generic required field error', }, // Navigation 'settingsTitle': 'Settings', '@settingsTitle': { 'description': 'Settings screen title', }, 'profileTitle': 'Profile', '@profileTitle': { 'description': 'Profile screen title', }, 'homeTitle': 'Home', '@homeTitle': { 'description': 'Home screen title', }, // Actions 'logoutButton': 'Logout', '@logoutButton': { 'description': 'Logout button label', }, 'deleteConfirmation': 'Are you sure you want to delete this item?', '@deleteConfirmation': { 'description': 'Delete confirmation message', }, }; // Add plurals if enabled if (usePlurals) { Object.assign(templateARB, { 'itemCount': '{count, plural, =0{No items} =1{1 item} other{{count} items}}', '@itemCount': { 'description': 'Item count with pluralization', 'placeholders': { 'count': { 'type': 'int', 'example': '5', }, }, }, 'daysRemaining': '{days, plural, =0{Today} =1{Tomorrow} other{In {days} days}}', '@daysRemaining': { 'description': 'Days remaining with pluralization', 'placeholders': { 'days': { 'type': 'int', }, }, }, }); } // Add gender rules if enabled if (useGender) { Object.assign(templateARB, { 'userGreeting': '{gender, select, male{Welcome Mr. {name}} female{Welcome Ms. {name}} other{Welcome {name}}}', '@userGreeting': { 'description': 'Greeting with gender selection', 'placeholders': { 'name': { 'type': 'String', }, 'gender': { 'type': 'String', }, }, }, }); } // Generate translations for other locales const translations = { es: { 'appTitle': 'Mi Aplicación Flutter', 'welcomeMessage': 'Bienvenido a nuestra aplicación', 'continueButton': 'Continuar', 'cancelButton': 'Cancelar', 'saveButton': 'Guardar', 'emailHint': 'Por favor ingrese su correo electrónico', 'passwordError': 'La contraseña debe tener al menos 8 caracteres', 'requiredField': 'Este campo es obligatorio', 'settingsTitle': 'Configuración', 'profileTitle': 'Perfil', 'homeTitle': 'Inicio', 'logoutButton': 'Cerrar sesión', 'deleteConfirmation': '¿Está seguro de que desea eliminar este elemento?', }, fr: { 'appTitle': 'Mon Application Flutter', 'welcomeMessage': 'Bienvenue dans notre application', 'continueButton': 'Continuer', 'cancelButton': 'Annuler', 'saveButton': 'Enregistrer', 'emailHint': 'Veuillez saisir votre email', 'passwordError': 'Le mot de passe doit contenir au moins 8 caractères', 'requiredField': 'Ce champ est obligatoire', 'settingsTitle': 'Paramètres', 'profileTitle': 'Profil', 'homeTitle': 'Accueil', 'logoutButton': 'Déconnexion', 'deleteConfirmation': 'Êtes-vous sûr de vouloir supprimer cet élément?', }, de: { 'appTitle': 'Meine Flutter App', 'welcomeMessage': 'Willkommen in unserer App', 'continueButton': 'Weiter', 'cancelButton': 'Abbrechen', 'saveButton': 'Speichern', 'emailHint': 'Bitte geben Sie Ihre E-Mail ein', 'passwordError': 'Das Passwort muss mindestens 8 Zeichen lang sein', 'requiredField': 'Dieses Feld ist erforderlich', 'settingsTitle': 'Einstellungen', 'profileTitle': 'Profil', 'homeTitle': 'Startseite', 'logoutButton': 'Abmelden', 'deleteConfirmation': 'Sind Sie sicher, dass Sie dieses Element löschen möchten?', }, }; const arbFiles = { 'app_en.arb': JSON.stringify(templateARB, null, 2), }; locales.filter(l => l !== 'en').forEach(locale => { const localeARB = { '@@locale': locale, ...translations[locale], }; if (usePlurals) { Object.assign(localeARB, getPluralsForLocale(locale)); } if (useGender) { Object.assign(localeARB, getGenderRulesForLocale(locale)); } arbFiles[`app_${locale}.arb`] = JSON.stringify(localeARB, null, 2); }); return arbFiles; } function getPluralsForLocale(locale) { const plurals = { es: { 'itemCount': '{count, plural, =0{Sin elementos} =1{1 elemento} other{{count} elementos}}', 'daysRemaining': '{days, plural, =0{Hoy} =1{Mañana} other{En {days} días}}', }, fr: { 'itemCount': '{count, plural, =0{Aucun élément} =1{1 élément} other{{count} éléments}}', 'daysRemaining': '{days, plural, =0{Aujourd\'hui} =1{Demain} other{Dans {days} jours}}', }, de: { 'itemCount': '{count, plural, =0{Keine Elemente} =1{1 Element} other{{count} Elemente}}', 'daysRemaining': '{days, plural, =0{Heute} =1{Morgen} other{In {days} Tagen}}', }, }; return plurals[locale] || {}; } function getGenderRulesForLocale(locale) { const genderRules = { es: { 'userGreeting': '{gender, select, male{Bienvenido Sr. {name}} female{Bienvenida Sra. {name}} other{Bienvenido/a {name}}}', }, fr: { 'userGreeting': '{gender, select, male{Bienvenue M. {name}} female{Bienvenue Mme {name}} other{Bienvenue {name}}}', }, de: { 'userGreeting': '{gender, select, male{Willkommen Herr {name}} female{Willkommen Frau {name}} other{Willkommen {name}}}', }, }; return genderRules[locale] || {}; } function generateIntegrationGuide() { return ` # Flutter Localization Integration Guide ## 1. Initial Setup 1. Add dependencies to pubspec.yaml 2. Create l10n.yaml in project root 3. Create lib/l10n directory 4. Add ARB files ## 2. Generate Localization Files \`\`\`bash flutter gen-l10n \`\`\` ## 3. Update Main App - Import generated localizations - Add localization delegates - Configure supported locales ## 4. Use in Widgets \`\`\`dart final l10n = AppLocalizations.of(context)!; Text(l10n.welcomeMessage) \`\`\` ## 5. Add Language Selector - Implement language picker - Save preference - Update app locale ## 6. Testing - Test all locales - Verify RTL layouts - Check text overflow - Test plurals and gender rules ## 7. Continuous Localization - Extract new strings regularly - Update translations - Use localization management tools `; } function generateAllL10nFiles(setup) { const files = {}; // Configuration files files['l10n.yaml'] = setup.configuration.l10nYaml; // ARB files Object.entries(setup.arbFiles).forEach(([filename, content]) => { files[`lib/l10n/${filename}`] = content; }); // Implementation files files['lib/main.dart'] = setup.implementation.mainApp; files['lib/core/localization/locale_provider.dart'] = setup.implementation.localeProvider; files['lib/core/localization/locale_service.dart'] = setup.implementation.localeService; files['lib/widgets/language_selector.dart'] = setup.implementation.languageSelector; // Export file files['lib/l10n/l10n.dart'] = ` export 'package:flutter_gen/gen_l10n/app_localizations.dart'; export '../core/localization/locale_provider.dart'; export '../core/localization/locale_service.dart';`; return files; } function generateL10nDocumentation(setup) { return ` # Localization Documentation ## Supported Languages ${setup.configuration.supportedLocales.map(l => `- ${getLocaleName(l)} (${l})`).join('\n')} ## File Structure ${setup.structure} ## Adding New Translations ### 1. Add to Template (app_en.arb) \`\`\`json "newKey": "English text", "@newKey": { "description": "Description for translators" } \`\`\` ### 2. Add Translations Update all app_*.arb files with translations ### 3. Generate Code \`\`\`bash flutter gen-l10n \`\`\` ### 4. Use in Code \`\`\`dart Text(l10n.newKey) \`\`\` ## Pluralization \`\`\`json "itemCount": "{count, plural, =0{No items} =1{One item} other{{count} items}}" \`\`\` ## Gender Rules \`\`\`json "greeting": "{gender, select, male{Mr.} female{Ms.} other{}} {name}" \`\`\` ## Date/Time Formatting \`\`\`dart final formattedDate = DateFormat.yMMMd(l10n.localeName).format(date); \`\`\` ## RTL Support - Automatic for Arabic, Hebrew, etc. - Use Directionality widget when needed - Test layouts in both directions ## Best Practices 1. Keep keys descriptive and hierarchical 2. Always add descriptions for translators 3. Use placeholders for dynamic content 4. Test with longest translations 5. Consider cultural differences `; } function getL10nCommands() { return { setup: [ 'flutter pub add flutter_localizations --sdk=flutter', 'flutter pub add intl', 'flutter pub get', ], generate: [ 'flutter gen-l10n', 'flutter gen-l10n --arb-dir=lib/l10n --output-dir=lib/generated', ], extract: [ '# Extract strings using external tools', 'flutter pub global activate intl_translation', 'flutter pub global run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart', ], validate: [ 'flutter analyze', 'flutter test', ], }; } function getL10nBestPractices() { return [ { category: 'Key Naming', practices: [ 'Use descriptive, hierarchical keys (e.g., settings_privacy_title)', 'Group related translations with common prefixes', 'Avoid generic keys like "message1", "text2"', 'Use camelCase for consistency', ], }, { category: 'Translation Management', practices: [ 'Keep source language (usually English) as source of truth', 'Add descriptions for context', 'Mark UI strings that should not be translated', 'Version control all ARB files', ], }, { category: 'Technical Considerations', practices: [ 'Handle missing translations gracefully', 'Test with longest possible translations', 'Consider text directionality (LTR/RTL)', 'Use proper date/time/number formatting', ], }, { category: 'Performance', practices: [ 'Lazy load translations for large apps', 'Cache selected locale', 'Minimize locale switching overhead', 'Use const widgets where possible', ], }, ]; } function recommendL10nTools() { return { management: [ { name: 'Crowdin', description: 'Professional localization platform', features: ['Translation memory', 'Screenshots', 'API integration'], }, { name: 'POEditor', description: 'Simple translation management', features: ['REST API', 'GitHub integration', 'Translation orders'], }, { name: 'Lokalise', description: 'Developer-friendly platform', features: ['Flutter SDK', 'OTA updates', 'Screenshots'], }, ], development: [ { name: 'Flutter Intl (VS Code)', description: 'IDE extension for Flutter i18n', features: ['ARB file management', 'Code generation', 'Extract strings'], }, { name: 'arb_editor', description: 'Desktop ARB file editor', features: ['Visual editing', 'Validation', 'Search/replace'], }, ], testing: [ { name: 'flutter_pseudo_localization', description: 'Test UI with pseudo translations', usage: 'Identify text overflow and hardcoded strings', }, ], }; } function getLocaleName(code) { const names = { 'en': 'English', 'es': 'Spanish', 'fr': 'French', 'de': 'German', 'it': 'Italian', 'pt': 'Portuguese', 'ru': 'Russian', 'zh': 'Chinese', 'ja': 'Japanese', 'ko': 'Korean', 'ar': 'Arabic', 'he': 'Hebrew', }; return names[code] || code.toUpperCase(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvillegastech/flutter_mcp_2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server