translocale_flutter 0.0.1
translocale_flutter: ^0.0.1 copied to clipboard
Flutter localization package for TransLocale service with over-the-air translations support. Simplifies internationalization and enables updating translations without app releases.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:translocale_example/l10n/translocale_extensions.dart';
import 'package:translocale_flutter/translocale_flutter.dart';
import 'l10n/generated/app_localizations.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize TransLocale with your API key
await TransLocale.initialize(
apiKey: 'tl_live_3mu0eOt79LnuL2DLyr7IBHD',
apiUrl: 'https://translocale-server-mnigboo-tayormi.globeapp.dev',
enableLogging: true,
checkForUpdates: true,
// Increase the timeout to 30 seconds to give the server more time to respond
requestTimeout: 30,
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Locale _currentLocale = const Locale('en');
bool _isLoading = false;
String _updateStatus = '';
List<Locale> _availableLocales = [];
bool _hasOtaUpdates = false;
// Add StreamSubscription for update events
StreamSubscription? _translationUpdateSubscription;
@override
void initState() {
super.initState();
_loadLocales();
// Listen for translation updates
_translationUpdateSubscription =
TransLocaleDelegateWrapper.onTranslationsUpdated.listen((_) {
// When we receive an update notification, refresh the UI
if (mounted) {
setState(() {
_hasOtaUpdates = true;
});
}
});
}
@override
void dispose() {
// Cancel the subscription when the widget is disposed
_translationUpdateSubscription?.cancel();
super.dispose();
}
Future<void> _loadLocales() async {
try {
// Get available locales from API
final locales = await TransLocale.getAvailableLocales();
setState(() {
_availableLocales =
locales.isNotEmpty ? locales : AppLocalizations.supportedLocales;
});
} catch (e) {
setState(() {
_availableLocales = AppLocalizations.supportedLocales;
});
}
}
Future<void> _updateTranslations() async {
setState(() {
_isLoading = true;
_updateStatus = 'Updating translations...';
});
try {
// Implement a simple retry mechanism
int retryCount = 0;
const maxRetries = 3;
bool success = false;
while (!success && retryCount < maxRetries) {
try {
// The TransLocaleDelegateWrapper will be notified automatically when this completes
await TransLocale.updateTranslations();
success = true;
} catch (e) {
retryCount++;
if (retryCount >= maxRetries) {
// If we've exhausted all retries, rethrow the error
rethrow;
}
// Update status to show retry attempt
setState(() {
_updateStatus = 'Retry attempt $retryCount of $maxRetries...';
});
// Wait a bit before retrying (exponential backoff)
await Future.delayed(Duration(seconds: 2 * retryCount));
}
}
setState(() {
_isLoading = false;
_updateStatus = 'Translations updated successfully!';
});
// No need to manually toggle locale - the notification system will handle it
} catch (e) {
setState(() {
_isLoading = false;
_updateStatus = 'Error: $e';
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TransLocale Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
locale: _currentLocale,
// Use supported locales from AppLocalizations
supportedLocales: AppLocalizations.supportedLocales,
// ✅ IMPORTANT: This delegate setup is key for OTA translations to work
localizationsDelegates: [
// TransLocale delegate must come first
TransLocaleDelegate(
supportedLocales: TransLocale.supportedLocales,
otaEnabled: true,
fallbackLocale: const Locale('en'),
),
// Then standard Flutter delegates
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: Scaffold(
appBar: AppBar(
title: const Text('TransLocale Example'),
backgroundColor: Colors.deepPurple.shade200,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'TransLocale Demo',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
// Locale switcher
Text(
'Available Locales:',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _availableLocales.map((locale) {
final isSelected =
_currentLocale.languageCode == locale.languageCode;
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ElevatedButton(
onPressed: () {
setState(() {
_currentLocale = locale;
});
},
style: ElevatedButton.styleFrom(
backgroundColor:
isSelected ? Colors.deepPurple : null,
foregroundColor: isSelected ? Colors.white : null,
),
child: Text(locale.toString()),
),
);
}).toList(),
),
),
const SizedBox(height: 24),
// Update translations button
ElevatedButton.icon(
onPressed: _isLoading ? null : _updateTranslations,
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
label: const Text('Update Translations'),
),
if (_updateStatus.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(_updateStatus),
),
const SizedBox(height: 24),
// Translations demo
Expanded(
child: Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Translation Examples',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
// ✅ Using the simple OTA extensions
Builder(builder: (context) {
final appLocalizations = AppLocalizations.of(context);
if (appLocalizations == null) {
return const Text('Localizations not ready');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Standard translations
Text('Standard Translation:',
style:
Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8),
Text(
'Welcome: ${appLocalizations.welcome_message}'),
const SizedBox(height: 16),
// OTA translations using extensions
Text('OTA Translation (with extension):',
style:
Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8),
// ✅ Simply use the Ota suffix on any property
Text(
'Welcome: ${appLocalizations.welcomeMessageOta}'),
Text(
'Theme: ${appLocalizations.settingsThemeTitleOta}'),
const SizedBox(height: 16),
if (_hasOtaUpdates)
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.green.shade200),
),
child: Row(
children: [
const Icon(Icons.check_circle,
color: Colors.green),
const SizedBox(width: 8),
Text(
'OTA translations active!',
style: TextStyle(
color: Colors.green.shade800),
),
],
),
),
],
);
}),
],
),
),
),
),
],
),
),
),
);
}
}