flutter_bhasha 1.0.1
flutter_bhasha: ^1.0.1 copied to clipboard
A production-grade Flutter localization package with dynamic language switching, JSON translations, RTL/LTR support, interpolation, fallback language, persistent storage, and a clean developer API.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_bhasha/flutter_bhasha.dart';
import 'package:provider/provider.dart';
// ── Supported locales ─────────────────────────────────────────────────────
const enLocale = BhashaLocale(
languageCode: 'en',
displayName: 'English',
nativeName: 'English',
flagEmoji: '🇺🇸',
);
const teLocale = BhashaLocale(
languageCode: 'te',
displayName: 'Telugu',
nativeName: 'తెలుగు',
flagEmoji: '🇮🇳',
);
const hiLocale = BhashaLocale(
languageCode: 'hi',
displayName: 'Hindi',
nativeName: 'हिन्दी',
flagEmoji: '🇮🇳',
);
const arLocale = BhashaLocale(
languageCode: 'ar',
displayName: 'Arabic',
nativeName: 'العربية',
flagEmoji: '🇸🇦',
textDirection: TextDirection.rtl,
);
// ── Entry point ───────────────────────────────────────────────────────────
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final controller = await BhashaController.initialize(
config: const BhashaConfig(
supportedLocales: [enLocale, teLocale, hiLocale, arLocale],
fallbackLocale: enLocale,
// Example app bundles its own translation files directly.
assetPath: 'assets/translations',
persistLocale: true,
useDeviceLocale: true,
logMissingKeys: true,
),
);
runApp(
BhashaProvider(
controller: controller,
child: const ExampleApp(),
),
);
}
// ── Root app ──────────────────────────────────────────────────────────────
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
final controller = context.watch<BhashaController>();
return MaterialApp(
title: 'flutter_bhasha Demo',
debugShowCheckedModeBanner: false,
locale: controller.currentLocale.toLocale(),
supportedLocales: controller.config.flutterLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
// ── Home screen ───────────────────────────────────────────────────────────
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _nameController = TextEditingController(text: 'Ravi');
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final controller = context.watch<BhashaController>();
final name = _nameController.text;
return Directionality(
textDirection: controller.textDirection,
child: Scaffold(
appBar: AppBar(
title: Text(context.tr('settings')),
actions: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: LanguageSelectorDropdown(
style: const TextStyle(fontSize: 14),
),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Greeting card ───────────────────────────────────────────
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.tr('hello'),
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 8),
Text(
context.tr('welcome', params: {'name': name}),
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
),
const SizedBox(height: 16),
// ── Name input ──────────────────────────────────────────────
TextField(
controller: _nameController,
decoration: InputDecoration(
labelText:
context.tr('auth.email').replaceAll('Email', 'Name'),
border: const OutlineInputBorder(),
),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 24),
// ── Translation showcase ────────────────────────────────────
_SectionHeader(title: 'Translations'),
_TranslationRow(key_: 'auth.login'),
_TranslationRow(key_: 'auth.logout'),
_TranslationRow(key_: 'auth.forgot_password'),
_TranslationRow(key_: 'errors.network'),
_TranslationRow(
key_: 'auth.welcome_back',
params: {'name': name},
),
_TranslationRow(
key_: 'home.greeting',
params: {'timeOfDay': 'morning', 'name': name},
),
const SizedBox(height: 24),
// ── Language picker list tile ───────────────────────────────
_SectionHeader(title: 'Language Picker'),
Card(
child: LanguageSelectorListTile(
title: context.tr('language'),
),
),
const SizedBox(height: 24),
// ── RTL / LTR indicator ─────────────────────────────────────
_SectionHeader(title: 'Text Direction'),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
controller.isRTL
? Icons.format_textdirection_r_to_l
: Icons.format_textdirection_l_to_r,
size: 28,
color: Colors.indigo,
),
const SizedBox(width: 12),
Text(
controller.isRTL
? 'Right-to-Left (RTL)'
: 'Left-to-Right (LTR)',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
),
const SizedBox(height: 24),
// ── Action buttons ──────────────────────────────────────────
_SectionHeader(title: 'Actions'),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () => controller.resetLocale(),
child: Text(context.tr('retry')),
),
OutlinedButton(
onPressed: () => controller.setLocaleByCode('te'),
child: const Text('Switch → తెలుగు'),
),
OutlinedButton(
onPressed: () => controller.setLocaleByCode('ar'),
child: const Text('Switch → العربية'),
),
],
),
],
),
),
),
);
}
}
// ── Helper widgets ────────────────────────────────────────────────────────
class _SectionHeader extends StatelessWidget {
const _SectionHeader({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(color: Colors.indigo, fontWeight: FontWeight.bold),
),
);
}
}
class _TranslationRow extends StatelessWidget {
const _TranslationRow({required this.key_, this.params});
final String key_;
final Map<String, dynamic>? params;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 180,
child: Text(
key_,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: Colors.grey,
),
),
),
Expanded(
child: Text(
context.tr(key_, params: params),
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}
}