mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-28 02:22:35 +02:00
feat: add support for 13 languages with improved language selector
- Rename Crowdin ARB files from locale-REGION to locale format - Fix @@locale values to match filenames - Update language selector to bottom sheet picker (supports 13 languages) - Supported: English, Indonesian, German, Spanish, French, Hindi, Japanese, Korean, Dutch, Portuguese, Russian, Chinese (Simplified/Traditional) - Remove duplicate app_id-ID.arb (keep app_id.arb)
This commit is contained in:
@@ -5,8 +5,18 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_de.dart';
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_es.dart';
|
||||
import 'app_localizations_fr.dart';
|
||||
import 'app_localizations_hi.dart';
|
||||
import 'app_localizations_id.dart';
|
||||
import 'app_localizations_ja.dart';
|
||||
import 'app_localizations_ko.dart';
|
||||
import 'app_localizations_nl.dart';
|
||||
import 'app_localizations_pt.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
@@ -94,8 +104,19 @@ abstract class AppLocalizations {
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('de'),
|
||||
Locale('en'),
|
||||
Locale('es'),
|
||||
Locale('fr'),
|
||||
Locale('hi'),
|
||||
Locale('id'),
|
||||
Locale('ja'),
|
||||
Locale('ko'),
|
||||
Locale('nl'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('zh'),
|
||||
Locale('zh', 'TW'),
|
||||
];
|
||||
|
||||
/// App name - DO NOT TRANSLATE
|
||||
@@ -3589,20 +3610,64 @@ class _AppLocalizationsDelegate
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en', 'id'].contains(locale.languageCode);
|
||||
bool isSupported(Locale locale) => <String>[
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'hi',
|
||||
'id',
|
||||
'ja',
|
||||
'ko',
|
||||
'nl',
|
||||
'pt',
|
||||
'ru',
|
||||
'zh',
|
||||
].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
// Lookup logic when language+country codes are specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'zh':
|
||||
{
|
||||
switch (locale.countryCode) {
|
||||
case 'TW':
|
||||
return AppLocalizationsZhTw();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'de':
|
||||
return AppLocalizationsDe();
|
||||
case 'en':
|
||||
return AppLocalizationsEn();
|
||||
case 'es':
|
||||
return AppLocalizationsEs();
|
||||
case 'fr':
|
||||
return AppLocalizationsFr();
|
||||
case 'hi':
|
||||
return AppLocalizationsHi();
|
||||
case 'id':
|
||||
return AppLocalizationsId();
|
||||
case 'ja':
|
||||
return AppLocalizationsJa();
|
||||
case 'ko':
|
||||
return AppLocalizationsKo();
|
||||
case 'nl':
|
||||
return AppLocalizationsNl();
|
||||
case 'pt':
|
||||
return AppLocalizationsPt();
|
||||
case 'ru':
|
||||
return AppLocalizationsRu();
|
||||
case 'zh':
|
||||
return AppLocalizationsZh();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@locale": "id",
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2026-01-16",
|
||||
"appName": "SpotiFLAC",
|
||||
"@appName": {
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@locale": "es-ES",
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2026-01-16",
|
||||
"appName": "SpotiFLAC",
|
||||
"@appName": {
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@locale": "pt-PT",
|
||||
"@@locale": "zh",
|
||||
"@@last_modified": "2026-01-16",
|
||||
"appName": "SpotiFLAC",
|
||||
"@appName": {
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@locale": "zh-CN",
|
||||
"@@locale": "zh_TW",
|
||||
"@@last_modified": "2026-01-16",
|
||||
"appName": "SpotiFLAC",
|
||||
"@appName": {
|
||||
@@ -709,48 +709,109 @@ class _LanguageSelector extends StatelessWidget {
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
static const _languages = [
|
||||
('system', 'System Default', Icons.phone_android),
|
||||
('en', 'English', Icons.language),
|
||||
('id', 'Bahasa Indonesia', Icons.language),
|
||||
('de', 'Deutsch', Icons.language),
|
||||
('es', 'Español', Icons.language),
|
||||
('fr', 'Français', Icons.language),
|
||||
('hi', 'हिन्दी', Icons.language),
|
||||
('ja', '日本語', Icons.language),
|
||||
('ko', '한국어', Icons.language),
|
||||
('nl', 'Nederlands', Icons.language),
|
||||
('pt', 'Português', Icons.language),
|
||||
('ru', 'Русский', Icons.language),
|
||||
('zh', '简体中文', Icons.language),
|
||||
('zh_TW', '繁體中文', Icons.language),
|
||||
];
|
||||
|
||||
String _getLanguageName(String code) {
|
||||
for (final lang in _languages) {
|
||||
if (lang.$1 == code) return lang.$2;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, bottom: 8),
|
||||
child: Text(
|
||||
context.l10n.appearanceLanguage,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.language,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
title: Text(context.l10n.appearanceLanguage),
|
||||
subtitle: Text(_getLanguageName(currentLocale)),
|
||||
trailing: Icon(
|
||||
Icons.chevron_right,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onTap: () => _showLanguagePicker(context),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLanguagePicker(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: colorScheme.surface,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
context.l10n.appearanceLanguage,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_ViewModeChip(
|
||||
icon: Icons.phone_android,
|
||||
label: context.l10n.languageSystem,
|
||||
isSelected: currentLocale == 'system',
|
||||
onTap: () => onChanged('system'),
|
||||
const Divider(height: 1),
|
||||
Flexible(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _languages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final lang = _languages[index];
|
||||
final isSelected = currentLocale == lang.$1;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
lang.$3,
|
||||
color: isSelected
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
title: Text(
|
||||
lang.$2,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurface,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? Icon(Icons.check, color: colorScheme.primary)
|
||||
: null,
|
||||
onTap: () {
|
||||
onChanged(lang.$1);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_ViewModeChip(
|
||||
icon: Icons.language,
|
||||
label: context.l10n.languageEnglish,
|
||||
isSelected: currentLocale == 'en',
|
||||
onTap: () => onChanged('en'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_ViewModeChip(
|
||||
icon: Icons.language,
|
||||
label: context.l10n.languageIndonesian,
|
||||
isSelected: currentLocale == 'id',
|
||||
onTap: () => onChanged('id'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user