feat(mobile): typed translation keys

This commit is contained in:
shenlong-tanwen 2025-06-05 18:49:18 +05:30
parent a9bd651692
commit b1ab8a610a
4 changed files with 2637 additions and 11 deletions

View File

@ -0,0 +1,61 @@
import 'dart:convert';
import 'dart:io';
const _kReservedWords = ['continue'];
void main() async {
final sourceFile = File('../i18n/en.json');
if (!await sourceFile.exists()) {
stderr.writeln('Source file does not exist');
return;
}
final outputDir = Directory('lib/generated');
await outputDir.create(recursive: true);
final outputFile = File('lib/generated/intl_keys.g.dart');
await _generate(sourceFile, 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>;
final buffer = StringBuffer('''
// DO NOT EDIT. This is code generated via generate_keys.dart
abstract class IntlKeys {
''');
_writeKeys(buffer, translations);
buffer.writeln('}');
await output.writeAsString(buffer.toString());
}
void _writeKeys(
StringBuffer buffer,
Map<String, dynamic> map, [
String prefix = '',
]) {
for (final entry in map.entries) {
final key = entry.key;
final value = entry.value;
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\';');
}
}
}
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}_';
return name;
}

2562
mobile/lib/generated/intl_keys.g.dart generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/partner.provider.dart'; import 'package:immich_mobile/providers/partner.provider.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
@ -41,13 +42,13 @@ class LibraryPage extends ConsumerWidget {
ActionButton( ActionButton(
onPressed: () => context.pushRoute(const FavoritesRoute()), onPressed: () => context.pushRoute(const FavoritesRoute()),
icon: Icons.favorite_outline_rounded, icon: Icons.favorite_outline_rounded,
label: 'favorites'.tr(), label: IntlKeys.favorites.tr(),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ActionButton( ActionButton(
onPressed: () => context.pushRoute(const ArchiveRoute()), onPressed: () => context.pushRoute(const ArchiveRoute()),
icon: Icons.archive_outlined, icon: Icons.archive_outlined,
label: 'archived'.tr(), label: IntlKeys.archived.tr(),
), ),
], ],
), ),
@ -58,14 +59,14 @@ class LibraryPage extends ConsumerWidget {
ActionButton( ActionButton(
onPressed: () => context.pushRoute(const SharedLinkRoute()), onPressed: () => context.pushRoute(const SharedLinkRoute()),
icon: Icons.link_outlined, icon: Icons.link_outlined,
label: 'shared_links'.tr(), label: IntlKeys.shared_links.tr(),
), ),
SizedBox(width: trashEnabled ? 8 : 0), SizedBox(width: trashEnabled ? 8 : 0),
trashEnabled trashEnabled
? ActionButton( ? ActionButton(
onPressed: () => context.pushRoute(const TrashRoute()), onPressed: () => context.pushRoute(const TrashRoute()),
icon: Icons.delete_outline_rounded, icon: Icons.delete_outline_rounded,
label: 'trash'.tr(), label: IntlKeys.trash.tr(),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
], ],
@ -133,7 +134,7 @@ class QuickAccessButtons extends ConsumerWidget {
size: 26, size: 26,
), ),
title: Text( title: Text(
'folders'.tr(), IntlKeys.folders.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@ -146,7 +147,7 @@ class QuickAccessButtons extends ConsumerWidget {
size: 26, size: 26,
), ),
title: Text( title: Text(
'locked_folder'.tr(), IntlKeys.locked_folder.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@ -159,7 +160,7 @@ class QuickAccessButtons extends ConsumerWidget {
size: 26, size: 26,
), ),
title: Text( title: Text(
'partners'.tr(), IntlKeys.partners.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@ -275,7 +276,7 @@ class PeopleCollectionCard extends ConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'people'.tr(), IntlKeys.people.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface, color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -343,7 +344,7 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'on_this_device'.tr(), IntlKeys.on_this_device.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface, color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -404,7 +405,7 @@ class PlacesCollectionCard extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'places'.tr(), IntlKeys.places.tr(),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface, color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@ -26,4 +26,6 @@ migration:
translation: translation:
dart run easy_localization:generate -S ../i18n dart run easy_localization:generate -S ../i18n
dart format lib/generated/codegen_loader.g.dart dart run bin/generate_keys.dart
dart format lib/generated/codegen_loader.g.dart
dart format lib/generated/intl_keys.g.dart