content localized on locale change

This commit is contained in:
2024-12-18 19:30:13 +01:00
parent a984fc15b0
commit 6587828a0b
9 changed files with 158 additions and 94 deletions

View File

@@ -1,36 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:resume/pages/landing_page.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:resume/providers/locale_provider.dart';
import 'package:resume/providers/content_provider.dart';
import 'theme.dart' show darkTheme;
void main() {
runApp(const Resume());
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LocaleProvider()),
ChangeNotifierProvider(create: (context) => ContentProvider()),
],
child: const Resume(),
),
);
}
class Resume extends StatelessWidget {
class Resume extends StatefulWidget {
const Resume({super.key});
@override
State<Resume> createState() => _ResumeState();
}
class _ResumeState extends State<Resume> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Resume',
theme: darkTheme,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
],
locale: const Locale('en'),
routes: {
'/': (context) => const LandingPage(),
},
initialRoute: '/',
return Consumer<LocaleProvider>(
builder: (context, localeProvider, child) => MaterialApp(
title: 'Resume',
theme: darkTheme,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
],
locale: localeProvider.locale,
routes: {
'/': (context) => const LandingPage(),
},
initialRoute: '/',
),
);
}
}

View File

@@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:resume/providers/locale_provider.dart';
import 'package:resume/services/breakpoints.dart';
import 'package:resume/widgets/language_dropdown.dart';
import 'package:resume/widgets/profile.dart';
@@ -6,7 +8,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:resume/constants.dart' show ContentType;
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../services/content_provider.dart';
import '../providers/content_provider.dart';
import '../widgets/content_block.dart';
class LandingPage extends StatefulWidget {
@@ -20,14 +22,18 @@ class LandingPage extends StatefulWidget {
class _LandingPageState extends State<LandingPage> {
bool loadingDone = false;
late ContentProvider contentProvider;
late Locale currentLocale;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await ContentProvider.init(context);
await Provider.of<ContentProvider>(context, listen: false)
.loadContent(context);
setState(() => loadingDone = true);
});
currentLocale = context.read<LocaleProvider>().locale;
}
double _getMainContentWidth() {
@@ -49,11 +55,13 @@ class _LandingPageState extends State<LandingPage> {
ContentBlock(
blockTitle: AppLocalizations.of(context)!.skills,
contentType: ContentType.skills,
content: contentProvider.getContent(ContentType.skills),
),
const Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBlock(
blockTitle: AppLocalizations.of(context)!.languages,
contentType: ContentType.language,
content: contentProvider.getContent(ContentType.language),
),
],
);
@@ -65,21 +73,25 @@ class _LandingPageState extends State<LandingPage> {
ContentBlock(
blockTitle: AppLocalizations.of(context)!.work_experience,
contentType: ContentType.experience,
content: contentProvider.getContent(ContentType.experience),
),
const Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBlock(
blockTitle: AppLocalizations.of(context)!.education,
contentType: ContentType.education,
content: contentProvider.getContent(ContentType.education),
),
const Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBlock(
blockTitle: AppLocalizations.of(context)!.additional_skills,
contentType: ContentType.generalSkills,
content: contentProvider.getContent(ContentType.generalSkills),
),
const Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBlock(
blockTitle: AppLocalizations.of(context)!.about_me,
contentType: ContentType.text,
content: contentProvider.getContent(ContentType.text),
),
],
);
@@ -188,6 +200,20 @@ class _LandingPageState extends State<LandingPage> {
@override
Widget build(BuildContext context) {
contentProvider = context.read<ContentProvider>();
if (currentLocale != context.read<LocaleProvider>().locale) {
loadingDone = false;
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Provider.of<ContentProvider>(context, listen: false)
.loadContent(context);
setState(() {
currentLocale = context.read<LocaleProvider>().locale;
loadingDone = true;
});
});
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.resume),

View File

@@ -0,0 +1,58 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:resume/constants.dart';
import 'package:resume/providers/locale_provider.dart';
class ContentProvider extends ChangeNotifier {
static final ContentProvider _instance = ContentProvider._();
factory ContentProvider() => _instance;
ContentProvider._();
static const String _baseJsonPath = 'assets/content/content';
Map<String, dynamic> _content = {
'experience': <List<dynamic>>[],
'education': <List<dynamic>>[],
'skills': <List<dynamic>>[],
'text': <String>[],
'general_skills': <String>[],
};
Future<bool> loadContent(BuildContext context) async {
String currentLocale = context.read<LocaleProvider>().locale.languageCode;
final String localizedPath = '${_baseJsonPath}_$currentLocale.json';
try {
String file = await rootBundle.loadString(localizedPath);
_content = json.decode(file);
notifyListeners();
return true;
} catch (e) {
// If localized version fails, fall back to default German
String file = await rootBundle.loadString('${_baseJsonPath}_de.json');
_content = json.decode(file);
notifyListeners();
return true;
}
}
T getContent<T>(ContentType contentType) {
switch (contentType) {
case ContentType.experience:
return _content['experience'] as T;
case ContentType.education:
return _content['education'] as T;
case ContentType.skills:
return _content['skills'] as T;
case ContentType.text:
return _content['text'] as T;
case ContentType.generalSkills:
return _content['general_skills'] as T;
default:
return [] as T;
}
}
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class LocaleProvider extends ChangeNotifier {
Locale _locale = const Locale('de');
Locale get locale => _locale;
void setLocale(Locale locale) {
_locale = locale;
notifyListeners();
}
}

View File

@@ -1,61 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:resume/constants.dart';
class ContentProvider {
ContentProvider._();
static const String _baseJsonPath = 'assets/content/content';
static Future<bool> init(BuildContext context) async {
try {
// Get the current locale
final String currentLocale = Localizations.localeOf(context).languageCode;
// Construct the path with the locale
final String localizedPath = '${_baseJsonPath}_$currentLocale.json';
// Try to load the localized version first
try {
String file = await rootBundle.loadString(localizedPath);
_content = json.decode(file);
return true;
} catch (e) {
// If localized version fails, fall back to default English
String file = await rootBundle.loadString('${_baseJsonPath}_de.json');
_content = json.decode(file);
return true;
}
} catch (e) {
print('Error loading content: $e');
return false;
}
}
static Map<String, dynamic> _content = {
'experience': <List<dynamic>>[],
'education': <List<dynamic>>[],
'skills': <List<dynamic>>[],
'text': <String>[],
'general_skills': <String>[],
};
static T getContent<T>(ContentType contentType) {
switch (contentType) {
case ContentType.experience:
return _content['experience'] as T;
case ContentType.education:
return _content['education'] as T;
case ContentType.skills:
return _content['skills'] as T;
case ContentType.text:
return _content['text'] as T;
case ContentType.generalSkills:
return _content['general_skills'] as T;
default:
return [] as T;
}
}
}

View File

@@ -3,32 +3,29 @@ import 'package:resume/widgets/content_list_tile.dart';
import 'package:resume/widgets/language_widget.dart';
import 'package:resume/constants.dart' show ContentType;
import '../services/content_provider.dart';
class ContentBlock extends StatelessWidget {
const ContentBlock({
super.key,
required this.blockTitle,
required this.contentType,
required this.content,
});
final String blockTitle;
final ContentType contentType;
final dynamic content;
Widget get _getContentWidget {
if (contentType == ContentType.language) {
return const LanguageWidget();
} else if (contentType == ContentType.text ||
contentType == ContentType.generalSkills) {
final content = ContentProvider.getContent<String>(contentType);
return SizedBox(
width: double.infinity,
child: Text(content),
);
}
// List-based content-blocks
List<dynamic> content =
ContentProvider.getContent<List<dynamic>>(contentType);
List<Widget> widgets = [];
for (var item in content) {
widgets.add(_buildListTile(item));

View File

@@ -1,13 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
class LanguageDropdown extends StatelessWidget {
import '../providers/locale_provider.dart';
class LanguageDropdown extends StatefulWidget {
const LanguageDropdown({super.key});
@override
State<LanguageDropdown> createState() => _LanguageDropdownState();
}
class _LanguageDropdownState extends State<LanguageDropdown> {
@override
Widget build(BuildContext context) {
return DropdownButton(
value: 'de',
LocaleProvider localeProvider = Provider.of<LocaleProvider>(context);
return DropdownButton<String>(
value: localeProvider.locale.languageCode,
items: [
DropdownMenuItem(
value: 'de',
@@ -24,12 +33,13 @@ class LanguageDropdown extends StatelessWidget {
),
),
],
onChanged: _onChanged,
onChanged: (value) {
if (value == null) return;
localeProvider.setLocale(Locale(value));
},
);
}
void _onChanged(dynamic value) {}
Widget getMenuItem(String label, String imagePath) {
return Row(
children: [