feat: move supported languages to DB table, expose via GET /languages
- migration 013: create languages table (code PK, native_name, english_name, is_active, sort_order) with all 12 existing languages seeded - locale: add Language struct, Languages []Language, LoadFromDB() — queries languages table at startup and populates both Supported map and Languages slice; existing Parse/FromContext/FromRequest unchanged - main.go: call locale.LoadFromDB after pool is ready - gemini/recipe.go: remove hardcoded langNames map, use locale.Languages linear lookup for English name in prompt - language/handler.go: new package with GET /languages handler returning active languages list (no auth required) - server.go: register GET /languages as public route - Flutter: add LanguageRepository + languageRepositoryProvider that fetches /languages from backend - language_provider.dart: replace const supportedLanguages map with supportedLanguagesProvider (FutureProvider) backed by LanguageRepository - profile_provider.dart: remove supportedLanguages.containsKey validation — backend is source of truth; sync any non-empty language from preferences - profile_screen.dart: use supportedLanguagesProvider for display name and dropdown (async with loading/error states) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import '../../core/locale/language_provider.dart';
|
||||
import '../../shared/models/user.dart';
|
||||
import 'profile_service.dart';
|
||||
|
||||
|
||||
class ProfileNotifier extends StateNotifier<AsyncValue<User>> {
|
||||
final ProfileService _service;
|
||||
final void Function(String) _setLanguage;
|
||||
@@ -33,7 +34,7 @@ class ProfileNotifier extends StateNotifier<AsyncValue<User>> {
|
||||
// Propagates the user's preferred language to the global languageProvider.
|
||||
void _syncLanguage() {
|
||||
final lang = state.valueOrNull?.preferences['language'] as String?;
|
||||
if (lang != null && supportedLanguages.containsKey(lang)) {
|
||||
if (lang != null && lang.isNotEmpty) {
|
||||
_setLanguage(lang);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ class _ProfileBody extends StatelessWidget {
|
||||
_InfoCard(children: [
|
||||
_InfoRow(
|
||||
'Язык',
|
||||
supportedLanguages[user.preferences['language'] as String? ?? 'ru'] ??
|
||||
ref.watch(supportedLanguagesProvider).valueOrNull?[
|
||||
user.preferences['language'] as String? ?? 'ru'] ??
|
||||
'Русский',
|
||||
),
|
||||
]),
|
||||
@@ -590,17 +591,23 @@ class _EditProfileSheetState extends ConsumerState<EditProfileSheet> {
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Language
|
||||
DropdownButtonFormField<String>(
|
||||
value: _language,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Язык интерфейса'),
|
||||
items: supportedLanguages.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e.key,
|
||||
child: Text(e.value),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _language = v),
|
||||
ref.watch(supportedLanguagesProvider).when(
|
||||
data: (languages) => DropdownButtonFormField<String>(
|
||||
value: _language,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Язык интерфейса'),
|
||||
items: languages.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e.key,
|
||||
child: Text(e.value),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _language = v),
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator()),
|
||||
error: (_, __) =>
|
||||
const Text('Не удалось загрузить языки'),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user