mirror of
https://github.com/immich-app/immich.git
synced 2025-12-24 09:14:58 +03:00
feat: typed translation generator (#24693)
* feat: typed translation generator * replace legacy key usage with new generated keys --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,99 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
const _kReservedWords = ['continue'];
|
||||
const _kReservedWords = [
|
||||
'abstract',
|
||||
'as',
|
||||
'assert',
|
||||
'async',
|
||||
'await',
|
||||
'break',
|
||||
'case',
|
||||
'catch',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'covariant',
|
||||
'default',
|
||||
'deferred',
|
||||
'do',
|
||||
'dynamic',
|
||||
'else',
|
||||
'enum',
|
||||
'export',
|
||||
'extends',
|
||||
'extension',
|
||||
'external',
|
||||
'factory',
|
||||
'false',
|
||||
'final',
|
||||
'finally',
|
||||
'for',
|
||||
'Function',
|
||||
'get',
|
||||
'hide',
|
||||
'if',
|
||||
'implements',
|
||||
'import',
|
||||
'in',
|
||||
'interface',
|
||||
'is',
|
||||
'late',
|
||||
'library',
|
||||
'mixin',
|
||||
'new',
|
||||
'null',
|
||||
'on',
|
||||
'operator',
|
||||
'part',
|
||||
'required',
|
||||
'rethrow',
|
||||
'return',
|
||||
'sealed',
|
||||
'set',
|
||||
'show',
|
||||
'static',
|
||||
'super',
|
||||
'switch',
|
||||
'sync',
|
||||
'this',
|
||||
'throw',
|
||||
'true',
|
||||
'try',
|
||||
'typedef',
|
||||
'var',
|
||||
'void',
|
||||
'when',
|
||||
'while',
|
||||
'with',
|
||||
'yield',
|
||||
];
|
||||
|
||||
const _kIntParamNames = [
|
||||
'count',
|
||||
'number',
|
||||
'amount',
|
||||
'total',
|
||||
'index',
|
||||
'size',
|
||||
'length',
|
||||
'width',
|
||||
'height',
|
||||
'year',
|
||||
'month',
|
||||
'day',
|
||||
'hour',
|
||||
'minute',
|
||||
'second',
|
||||
'page',
|
||||
'limit',
|
||||
'offset',
|
||||
'max',
|
||||
'min',
|
||||
'id',
|
||||
'num',
|
||||
'quantity',
|
||||
];
|
||||
|
||||
void main() async {
|
||||
final sourceFile = File('../i18n/en.json');
|
||||
@@ -15,49 +107,258 @@ void main() async {
|
||||
final outputDir = Directory('lib/generated');
|
||||
await outputDir.create(recursive: true);
|
||||
|
||||
final outputFile = File('lib/generated/intl_keys.g.dart');
|
||||
await _generate(sourceFile, outputFile);
|
||||
final content = await sourceFile.readAsString();
|
||||
final translations = json.decode(content) as Map<String, dynamic>;
|
||||
|
||||
final outputFile = File('lib/generated/translations.g.dart');
|
||||
await _generateTranslations(translations, outputFile);
|
||||
print('Generated ${outputFile.path}');
|
||||
}
|
||||
|
||||
Future<void> _generate(File source, File output) async {
|
||||
final content = await source.readAsString();
|
||||
final translations = json.decode(content) as Map<String, dynamic>;
|
||||
class TranslationNode {
|
||||
final String key;
|
||||
final String? value;
|
||||
final Map<String, TranslationNode> children;
|
||||
final List<TranslationParam> params;
|
||||
|
||||
const TranslationNode({
|
||||
required this.key,
|
||||
this.value,
|
||||
Map<String, TranslationNode>? children,
|
||||
List<TranslationParam>? params,
|
||||
}) : children = children ?? const {},
|
||||
params = params ?? const [];
|
||||
|
||||
bool get isLeaf => value != null;
|
||||
bool get hasParams => params.isNotEmpty;
|
||||
}
|
||||
|
||||
class TranslationParam {
|
||||
final String name;
|
||||
final String type;
|
||||
|
||||
const TranslationParam(this.name, this.type);
|
||||
}
|
||||
|
||||
Future<void> _generateTranslations(Map<String, dynamic> translations, File output) async {
|
||||
final root = _buildTranslationTree('', translations);
|
||||
|
||||
final buffer = StringBuffer('''
|
||||
// DO NOT EDIT. This is code generated via generate_keys.dart
|
||||
|
||||
abstract class IntlKeys {
|
||||
''');
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/message_format.dart';
|
||||
|
||||
_writeKeys(buffer, translations);
|
||||
buffer.writeln('}');
|
||||
|
||||
await output.writeAsString(buffer.toString());
|
||||
extension TranslationsExtension on BuildContext {
|
||||
Translations get t => Translations.of(this);
|
||||
}
|
||||
|
||||
void _writeKeys(
|
||||
StringBuffer buffer,
|
||||
Map<String, dynamic> map, [
|
||||
String prefix = '',
|
||||
]) {
|
||||
for (final entry in map.entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
class StaticTranslations {
|
||||
StaticTranslations._();
|
||||
static final instance = Translations._(null);
|
||||
}
|
||||
|
||||
if (value is Map<String, dynamic>) {
|
||||
_writeKeys(buffer, value, prefix.isEmpty ? key : '${prefix}_$key');
|
||||
} else {
|
||||
final name = _cleanName(prefix.isEmpty ? key : '${prefix}_$key');
|
||||
final path = prefix.isEmpty ? key : '$prefix.$key'.replaceAll('_', '.');
|
||||
buffer.writeln(' static const $name = \'$path\';');
|
||||
abstract class _BaseTranslations {
|
||||
BuildContext? get _context;
|
||||
|
||||
String _t(String key, [Map<String, Object>? args]) {
|
||||
if (key.isEmpty) return '';
|
||||
try {
|
||||
final translated = key.tr(context: _context);
|
||||
return args != null
|
||||
? MessageFormat(translated, locale: Intl.defaultLocale ?? 'en').format(args)
|
||||
: translated;
|
||||
} catch (e) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _cleanName(String name) {
|
||||
name = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||||
if (RegExp(r'^[0-9]').hasMatch(name)) name = 'k_$name';
|
||||
if (_kReservedWords.contains(name)) name = '${name}_';
|
||||
class Translations extends _BaseTranslations {
|
||||
@override
|
||||
final BuildContext? _context;
|
||||
Translations._(this._context);
|
||||
|
||||
static Translations of(BuildContext context) {
|
||||
context.locale;
|
||||
return Translations._(context);
|
||||
}
|
||||
|
||||
''');
|
||||
|
||||
_generateClassMembers(buffer, root, ' ');
|
||||
buffer.writeln('}');
|
||||
_generateNestedClasses(buffer, root);
|
||||
|
||||
await output.writeAsString(buffer.toString());
|
||||
}
|
||||
|
||||
TranslationNode _buildTranslationTree(String key, dynamic value) {
|
||||
if (value is Map<String, dynamic>) {
|
||||
final children = <String, TranslationNode>{};
|
||||
for (final entry in value.entries) {
|
||||
children[entry.key] = _buildTranslationTree(entry.key, entry.value);
|
||||
}
|
||||
return TranslationNode(key: key, children: children);
|
||||
} else {
|
||||
final stringValue = value.toString();
|
||||
final params = _extractParams(stringValue);
|
||||
return TranslationNode(key: key, value: stringValue, params: params);
|
||||
}
|
||||
}
|
||||
|
||||
List<TranslationParam> _extractParams(String value) {
|
||||
final params = <String, TranslationParam>{};
|
||||
|
||||
final icuRegex = RegExp(r'\{(\w+),\s*(plural|select|number|date|time)([^}]*(?:\{[^}]*\}[^}]*)*)\}');
|
||||
for (final match in icuRegex.allMatches(value)) {
|
||||
final name = match.group(1)!;
|
||||
final icuType = match.group(2)!;
|
||||
final icuContent = match.group(3) ?? '';
|
||||
|
||||
if (params.containsKey(name)) continue;
|
||||
|
||||
String type;
|
||||
if (icuType == 'plural' || icuType == 'number') {
|
||||
type = 'int';
|
||||
} else if (icuType == 'select') {
|
||||
final hasTrueFalse = RegExp(r',\s*(true|false)\s*\{').hasMatch(icuContent);
|
||||
type = hasTrueFalse ? 'bool' : 'String';
|
||||
} else {
|
||||
type = 'String';
|
||||
}
|
||||
|
||||
params[name] = TranslationParam(name, type);
|
||||
}
|
||||
|
||||
var cleanedValue = value;
|
||||
var depth = 0;
|
||||
var icuStart = -1;
|
||||
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
if (value[i] == '{') {
|
||||
if (depth == 0) icuStart = i;
|
||||
depth++;
|
||||
} else if (value[i] == '}') {
|
||||
depth--;
|
||||
if (depth == 0 && icuStart >= 0) {
|
||||
final block = value.substring(icuStart, i + 1);
|
||||
if (RegExp(r'^\{\w+,').hasMatch(block)) {
|
||||
cleanedValue = cleanedValue.replaceFirst(block, '');
|
||||
}
|
||||
icuStart = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final simpleRegex = RegExp(r'\{(\w+)\}');
|
||||
for (final match in simpleRegex.allMatches(cleanedValue)) {
|
||||
final name = match.group(1)!;
|
||||
|
||||
if (params.containsKey(name)) continue;
|
||||
|
||||
String type;
|
||||
if (_kIntParamNames.contains(name.toLowerCase())) {
|
||||
type = 'int';
|
||||
} else {
|
||||
type = 'Object';
|
||||
}
|
||||
|
||||
params[name] = TranslationParam(name, type);
|
||||
}
|
||||
|
||||
return params.values.toList();
|
||||
}
|
||||
|
||||
void _generateClassMembers(StringBuffer buffer, TranslationNode node, String indent, [String keyPrefix = '']) {
|
||||
final sortedKeys = node.children.keys.toList()..sort();
|
||||
|
||||
for (final childKey in sortedKeys) {
|
||||
final child = node.children[childKey]!;
|
||||
final dartName = _escapeName(childKey);
|
||||
final fullKey = keyPrefix.isEmpty ? childKey : '$keyPrefix.$childKey';
|
||||
|
||||
if (child.isLeaf) {
|
||||
if (child.hasParams) {
|
||||
_generateMethod(buffer, dartName, fullKey, child.params, indent);
|
||||
} else {
|
||||
_generateGetter(buffer, dartName, fullKey, indent);
|
||||
}
|
||||
} else {
|
||||
final className = _toNestedClassName(keyPrefix, childKey);
|
||||
buffer.writeln('${indent}late final $dartName = $className._(_context);');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _generateGetter(StringBuffer buffer, String dartName, String translationKey, String indent) {
|
||||
buffer.writeln('${indent}String get $dartName => _t(\'$translationKey\');');
|
||||
}
|
||||
|
||||
void _generateMethod(
|
||||
StringBuffer buffer,
|
||||
String dartName,
|
||||
String translationKey,
|
||||
List<TranslationParam> params,
|
||||
String indent,
|
||||
) {
|
||||
final paramList = params.map((p) => 'required ${p.type} ${_escapeName(p.name)}').join(', ');
|
||||
final argsMap = params.map((p) => '\'${p.name}\': ${_escapeName(p.name)}').join(', ');
|
||||
buffer.writeln('${indent}String $dartName({$paramList}) => _t(\'$translationKey\', {$argsMap});');
|
||||
}
|
||||
|
||||
void _generateNestedClasses(StringBuffer buffer, TranslationNode node, [String keyPrefix = '']) {
|
||||
final sortedKeys = node.children.keys.toList()..sort();
|
||||
|
||||
for (final childKey in sortedKeys) {
|
||||
final child = node.children[childKey]!;
|
||||
final fullKey = keyPrefix.isEmpty ? childKey : '$keyPrefix.$childKey';
|
||||
|
||||
if (!child.isLeaf && child.children.isNotEmpty) {
|
||||
final className = _toNestedClassName(keyPrefix, childKey);
|
||||
buffer.writeln();
|
||||
buffer.writeln('class $className extends _BaseTranslations {');
|
||||
buffer.writeln(' @override');
|
||||
buffer.writeln(' final BuildContext? _context;');
|
||||
buffer.writeln(' $className._(this._context);');
|
||||
_generateClassMembers(buffer, child, ' ', fullKey);
|
||||
buffer.writeln('}');
|
||||
_generateNestedClasses(buffer, child, fullKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _toNestedClassName(String prefix, String key) {
|
||||
final parts = <String>[];
|
||||
if (prefix.isNotEmpty) {
|
||||
parts.addAll(prefix.split('.'));
|
||||
}
|
||||
parts.add(key);
|
||||
|
||||
final result = StringBuffer('_');
|
||||
for (final part in parts) {
|
||||
final words = part.split('_');
|
||||
for (final word in words) {
|
||||
if (word.isNotEmpty) {
|
||||
result.write(word[0].toUpperCase());
|
||||
if (word.length > 1) {
|
||||
result.write(word.substring(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.write('Translations');
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
String _escapeName(String name) {
|
||||
if (_kReservedWords.contains(name)) {
|
||||
return '$name\$';
|
||||
}
|
||||
if (RegExp(r'^[0-9]').hasMatch(name)) {
|
||||
return 'k$name';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ import 'package:immich_mobile/constants/locales.dart';
|
||||
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
@@ -217,8 +216,8 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
||||
ref
|
||||
.read(backgroundWorkerFgServiceProvider)
|
||||
.saveNotificationMessage(
|
||||
IntlKeys.uploading_media.t(),
|
||||
IntlKeys.backup_background_service_default_notification.t(),
|
||||
StaticTranslations.instance.uploading_media,
|
||||
StaticTranslations.instance.backup_background_service_default_notification,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||
@@ -153,7 +153,7 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
Icon(Icons.warning_rounded, color: context.colorScheme.error, fill: 1),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
IntlKeys.backup_error_sync_failed.t(),
|
||||
context.t.backup_error_sync_failed,
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.error),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
|
||||
class SettingsHeader {
|
||||
String key = "";
|
||||
@@ -61,7 +61,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(IntlKeys.headers_settings_tile_title).tr(),
|
||||
title: Text(context.t.headers_settings_tile_title),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/partner.provider.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
@@ -41,13 +41,13 @@ class LibraryPage extends ConsumerWidget {
|
||||
ActionButton(
|
||||
onPressed: () => context.pushRoute(const FavoritesRoute()),
|
||||
icon: Icons.favorite_outline_rounded,
|
||||
label: IntlKeys.favorites.tr(),
|
||||
label: context.t.favorites,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionButton(
|
||||
onPressed: () => context.pushRoute(const ArchiveRoute()),
|
||||
icon: Icons.archive_outlined,
|
||||
label: IntlKeys.archived.tr(),
|
||||
label: context.t.archived,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -58,14 +58,14 @@ class LibraryPage extends ConsumerWidget {
|
||||
ActionButton(
|
||||
onPressed: () => context.pushRoute(const SharedLinkRoute()),
|
||||
icon: Icons.link_outlined,
|
||||
label: IntlKeys.shared_links.tr(),
|
||||
label: context.t.shared_links,
|
||||
),
|
||||
SizedBox(width: trashEnabled ? 8 : 0),
|
||||
trashEnabled
|
||||
? ActionButton(
|
||||
onPressed: () => context.pushRoute(const TrashRoute()),
|
||||
icon: Icons.delete_outline_rounded,
|
||||
label: IntlKeys.trash.tr(),
|
||||
label: context.t.trash,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
@@ -120,26 +120,20 @@ class QuickAccessButtons extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
leading: const Icon(Icons.folder_outlined, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.folders.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
title: Text(context.t.folders, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)),
|
||||
onTap: () => context.pushRoute(FolderRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.lock_outline_rounded, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.locked_folder.tr(),
|
||||
context.t.locked_folder,
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () => context.pushRoute(const LockedRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.group_outlined, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.partners.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
title: Text(context.t.partners, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)),
|
||||
onTap: () => context.pushRoute(const PartnerRoute()),
|
||||
),
|
||||
PartnerList(partners: partners),
|
||||
@@ -235,7 +229,7 @@ class PeopleCollectionCard extends ConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
IntlKeys.people.tr(),
|
||||
context.t.people,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -295,7 +289,7 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
IntlKeys.on_this_device.tr(),
|
||||
context.t.on_this_device,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -346,7 +340,7 @@ class PlacesCollectionCard extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
IntlKeys.places.tr(),
|
||||
context.t.places,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
@@ -43,9 +44,7 @@ class DriftTrashPage extends StatelessWidget {
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: const Text("trash_page_info").t(context: context, args: {"days": "$trashDays"}),
|
||||
),
|
||||
sliver: SliverToBoxAdapter(child: Text(context.t.trash_page_info(days: trashDays))),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class CustomProxyHeaderSettings extends StatelessWidget {
|
||||
@@ -15,11 +14,11 @@ class CustomProxyHeaderSettings extends StatelessWidget {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
dense: true,
|
||||
title: Text(
|
||||
IntlKeys.advanced_settings_proxy_headers_title.tr(),
|
||||
context.t.advanced_settings_proxy_headers_title,
|
||||
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(
|
||||
IntlKeys.advanced_settings_proxy_headers_subtitle.tr(),
|
||||
context.t.advanced_settings_proxy_headers_subtitle,
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
onTap: () => context.pushRoute(const HeaderSettingsRoute()),
|
||||
|
||||
@@ -37,7 +37,7 @@ translation:
|
||||
dart run easy_localization:generate -S ../i18n
|
||||
dart run bin/generate_keys.dart
|
||||
dart format lib/generated/codegen_loader.g.dart
|
||||
dart format lib/generated/intl_keys.g.dart
|
||||
dart format lib/generated/translations.g.dart
|
||||
|
||||
analyze:
|
||||
dart analyze --fatal-infos
|
||||
|
||||
@@ -32,13 +32,7 @@ depends = [
|
||||
[tasks."codegen:translation"]
|
||||
alias = "translation"
|
||||
description = "Generate translations from i18n JSONs"
|
||||
run = [
|
||||
{ task = "//i18n:format-fix" },
|
||||
{ tasks = [
|
||||
"i18n:loader",
|
||||
"i18n:keys",
|
||||
] },
|
||||
]
|
||||
run = [{ task = "//i18n:format-fix" }, { tasks = ["i18n:loader", "i18n:keys"] }]
|
||||
|
||||
[tasks."codegen:app-icon"]
|
||||
description = "Generate app icons"
|
||||
@@ -158,10 +152,10 @@ run = [
|
||||
description = "Generate i18n keys"
|
||||
hide = true
|
||||
sources = ["i18n/en.json"]
|
||||
outputs = "lib/generated/intl_keys.g.dart"
|
||||
outputs = "lib/generated/translations.g.dart"
|
||||
run = [
|
||||
"dart run bin/generate_keys.dart",
|
||||
"dart format lib/generated/intl_keys.g.dart",
|
||||
"dart format lib/generated/translations.g.dart",
|
||||
]
|
||||
|
||||
[tasks."analyze:dart"]
|
||||
|
||||
Reference in New Issue
Block a user