smart_localization 0.0.2
smart_localization: ^0.0.2 copied to clipboard
A backend-agnostic smart localization system for Flutter. Version-controlled translations with intelligent caching, fallback chains, and parameterized translations.
import 'package:flutter/material.dart';
import 'package:smart_localization/smart_localization.dart';
// --- MOCK BACKEND IMPLEMENTATION ---
class MockBackend implements TranslationBackend {
int _version = 1;
final Map<String, Map<String, String>> _database = {
'en': {
'home.title': 'Smart Localization',
'home.greeting': 'Hello, User!',
},
'tr': {
'home.title': 'Akıllı Yerelleştirme',
'home.greeting': 'Merhaba, Kullanıcı!',
},
};
@override
Future<TranslationData?> fetchTranslations(String languageCode) async {
final translations = _database[languageCode];
if (translations == null) return null;
return TranslationData(
translations: Map.from(translations),
version: _version,
);
}
@override
Future<int> getRemoteVersion() async {
return _version;
}
@override
Future<List<String>> getSupportedLanguages() async => _database.keys.toList();
void updateTranslation(String lang, String key, String value) {
if (_database[lang] != null) _database[lang]![key] = value;
}
void publishChanges() {
_version++;
}
Map<String, String> getRawData(String lang) => _database[lang] ?? {};
}
final mockBackend = MockBackend();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SmartLocalization.initialize(
backend: mockBackend,
config: const SmartLocalizationConfig(
defaultLanguage: 'en',
enableLogging: true,
),
);
runApp(const MyApp());
}
// SpeakBlend Authentic Colors & Typography
class SpeakblendColors {
static const Color backgroundColor = Color.fromARGB(255, 5, 11, 11);
static const Color blackColor = Color.fromARGB(255, 5, 11, 11);
static const Color whiteColor = Colors.white;
static const Color whiteTextColor = Color(0XFFE0E0E0);
static const Color borderColor = Color(0XFF2F2F2F);
static const Color searchBarColor = Color.fromARGB(255, 24, 24, 27);
static const Color primaryColor = Color.fromARGB(255, 255, 36, 36);
static const Color grayColor = Color.fromARGB(255, 58, 61, 64);
static const Color darkGrayTextColor = Color.fromARGB(255, 103, 109, 114);
}
class SpeakblendTheme {
static TextStyle regularText({required Color color, double? fontSize}) =>
TextStyle(
fontFamily: 'Inter',
color: color,
fontSize: fontSize,
fontWeight: FontWeight.w400,
letterSpacing: -0.3);
static TextStyle mediumText({required Color color, double? fontSize}) =>
TextStyle(
fontFamily: 'Inter',
color: color,
fontSize: fontSize,
fontWeight: FontWeight.w500,
letterSpacing: -0.3);
static TextStyle semiBoldText({required Color color, double? fontSize}) =>
TextStyle(
fontFamily: 'Inter',
color: color,
fontSize: fontSize,
fontWeight: FontWeight.w600,
letterSpacing: -0.4);
static TextStyle boldText({required Color color, double? fontSize}) =>
TextStyle(
fontFamily: 'Inter',
color: color,
fontSize: fontSize,
fontWeight: FontWeight.w700,
letterSpacing: -0.5);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Smart Localization Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.dark,
scaffoldBackgroundColor: SpeakblendColors.backgroundColor,
colorSchemeSeed: SpeakblendColors.primaryColor,
useMaterial3: true,
fontFamily: 'Inter',
),
home: const DemoScreen(),
);
}
}
class DemoScreen extends StatefulWidget {
const DemoScreen({super.key});
@override
State<DemoScreen> createState() => _DemoScreenState();
}
class _DemoScreenState extends State<DemoScreen> {
final _languages = ['en', 'tr'];
int _langIndex = 0;
bool _isCheckingUpdates = false;
// Backend Controller
final TextEditingController _titleController = TextEditingController();
final TextEditingController _greetingController = TextEditingController();
@override
void initState() {
super.initState();
_loadBackendData();
}
void _loadBackendData() {
final lang = _languages[_langIndex];
final data = mockBackend.getRawData(lang);
_titleController.text = data['home.title'] ?? '';
_greetingController.text = data['home.greeting'] ?? '';
}
Future<void> _switchLanguage() async {
_langIndex = (_langIndex + 1) % _languages.length;
await SmartLocalization.instance.setLanguage(_languages[_langIndex]);
_loadBackendData();
setState(() {});
}
Future<void> _publishAndSync() async {
setState(() => _isCheckingUpdates = true);
// 1. Save to Backend
final lang = _languages[_langIndex];
mockBackend.updateTranslation(lang, 'home.title', _titleController.text);
mockBackend.updateTranslation(
lang, 'home.greeting', _greetingController.text);
mockBackend.publishChanges();
// 2. Client checks for updates instantly (bypass throttle)
await SmartLocalization.instance.refreshTranslations();
if (mounted) setState(() => _isCheckingUpdates = false);
}
@override
Widget build(BuildContext context) {
final langCode = SmartLocalization.instance.currentLanguage;
final langName = SmartLocalization.instance.getLanguageName(langCode);
return Scaffold(
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 24),
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ==========================
// 1. FRONTEND APP UI
// ==========================
Text('User App View',
style: SpeakblendTheme.boldText(
color: SpeakblendColors.darkGrayTextColor,
fontSize: 14)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: SpeakblendColors.searchBarColor,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: SpeakblendColors.borderColor),
),
child: Column(
children: [
// Logo
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: SpeakblendColors.backgroundColor,
border:
Border.all(color: SpeakblendColors.borderColor),
),
child: const Icon(Icons.translate_rounded,
color: SpeakblendColors.whiteColor),
),
const SizedBox(height: 24),
// Translations
Text(
context.localization('Smart Localization',
id: 'home.title'),
textAlign: TextAlign.center,
style: SpeakblendTheme.boldText(
color: SpeakblendColors.whiteColor, fontSize: 24),
),
const SizedBox(height: 8),
Text(
context.localization('Hello, User!',
id: 'home.greeting'),
textAlign: TextAlign.center,
style: SpeakblendTheme.mediumText(
color: SpeakblendColors.whiteTextColor,
fontSize: 16),
),
const SizedBox(height: 24),
// Language Switcher Toggle
GestureDetector(
onTap: _switchLanguage,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: SpeakblendColors.borderColor,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.language_rounded,
size: 16, color: SpeakblendColors.whiteColor),
const SizedBox(width: 8),
Text(langName.toUpperCase(),
style: SpeakblendTheme.mediumText(
color: SpeakblendColors.whiteColor,
fontSize: 12)),
],
),
),
),
],
),
),
const SizedBox(height: 48),
// ==========================
// 2. BACKEND ADMIN UI
// ==========================
Text('Backend Database ($langCode)',
style: SpeakblendTheme.boldText(
color: SpeakblendColors.darkGrayTextColor,
fontSize: 14)),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: SpeakblendColors.backgroundColor,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: SpeakblendColors.borderColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Field 1
Text('home.title',
style: SpeakblendTheme.mediumText(
color: SpeakblendColors.darkGrayTextColor,
fontSize: 12)),
const SizedBox(height: 8),
_buildTextField(_titleController),
const SizedBox(height: 16),
// Field 2
Text('home.greeting',
style: SpeakblendTheme.mediumText(
color: SpeakblendColors.darkGrayTextColor,
fontSize: 12)),
const SizedBox(height: 8),
_buildTextField(_greetingController),
const SizedBox(height: 24),
// Publish Button
_SpeakBlendButton(
text: _isCheckingUpdates
? 'Syncing...'
: 'Update & Sync App',
icon: Icons.sync_rounded,
backgroundColor: SpeakblendColors.whiteColor,
textColor: SpeakblendColors.blackColor,
isLoading: _isCheckingUpdates,
onTap: _publishAndSync,
),
],
),
),
],
),
),
),
),
);
}
Widget _buildTextField(TextEditingController controller) {
return TextField(
controller: controller,
style: SpeakblendTheme.regularText(
color: SpeakblendColors.whiteColor, fontSize: 16),
decoration: InputDecoration(
filled: true,
fillColor: SpeakblendColors.searchBarColor,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: SpeakblendColors.borderColor)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: SpeakblendColors.primaryColor)),
),
);
}
}
class _SpeakBlendButton extends StatelessWidget {
final String text;
final IconData? icon;
final Color backgroundColor;
final Color textColor;
final VoidCallback onTap;
final bool isLoading;
const _SpeakBlendButton(
{required this.text,
this.icon,
required this.backgroundColor,
required this.textColor,
required this.onTap,
this.isLoading = false});
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Stack(
children: [
GestureDetector(
onTap: isLoading ? null : onTap,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.1, 0.8],
colors: [
SpeakblendColors.grayColor.withAlpha(128),
backgroundColor
],
),
borderRadius: BorderRadius.circular(24),
),
child: Container(
height: 56,
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(22)),
child: isLoading
? Center(
child: SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor:
AlwaysStoppedAnimation<Color>(textColor))))
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, color: textColor, size: 24),
const SizedBox(width: 8)
],
Text(text,
style: SpeakblendTheme.mediumText(
color: textColor, fontSize: 16)),
],
),
),
),
),
],
),
);
}
}