Internationalization (German) of the mobile app. (#246)

* Add i18n framework to mobile app and write simple translation generator

* Replace all texts in login_form with i18n keys

* Localization of sharing section

* Localization of asset viewer section

* Use JSON as base translation format

* Add check for missing/unused translation keys

* Add localizely

* Remove i18n directory in favour of localizely

* Backup Translation

* More translations

* Translate home page

* Translation of search page

* Translate new server version announcement

* Reformat code

* Fix typo in german translation

* Update englisch translations

* Change translation keys to match dart filenames

* Add /api to translated endpoint_urls

* Update localizely.yml

* Add languages to ios plist

* Remove unused keys

* Added script to check outdated key in other translations

* Add download key to localizely.yml

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Matthias Rupp 2022-07-07 20:40:54 +02:00 committed by GitHub
parent f3032f74a4
commit 2b5cef156c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 601 additions and 213 deletions

15
localizely.yml Normal file
View File

@ -0,0 +1,15 @@
config_version: 1.0
project_id: ead34689-ec52-41d9-b675-09bc85a6cbd7
file_type: flutter_arb
upload:
files:
- file: mobile/assets/i18n/en-US.json
locale_code: en
- file: mobile/assets/i18n/de-DE.json
locale_code: de
download:
files:
- file: mobile/assets/i18n/en-US.json
locale_code: en
- file: mobile/assets/i18n/de-DE.json
locale_code: de

View File

@ -0,0 +1,98 @@
{
"date_format": "E d. LLL y \u2022 hh:mm",
"daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy",
"monthly_title_text_date_format": "MMMM y",
"login_form_button_text": "Anmelden",
"login_form_save_login": "Angemeldet bleiben",
"login_form_endpoint_url": "Server URL",
"login_form_endpoint_hint": "http://deine-server-ip:port/api",
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
"login_form_err_leading_whitespace": "Führendes Leerzichen",
"login_form_err_invalid_email": "Ungültige E-Mail",
"login_form_err_http": "Bitte gebe http:// oder https:// an",
"login_form_label_email": "E-Mail",
"login_form_email_hint": "deine@email.de",
"login_form_label_password": "Passwort",
"login_form_password_hint": "password",
"share_add_title": "Titel hinzufügen",
"album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden",
"album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden",
"album_viewer_appbar_share_err_remove": "Beim Löschen von Elementen aus dem Album ist ein Problem aufgetreten",
"album_viewer_appbar_share_err_title": "Der Titel konnte nicht geändert werden",
"album_viewer_appbar_share_remove": "Entferne vom Album",
"album_viewer_appbar_share_delete": "Album löschen",
"album_viewer_appbar_share_leave": "Album verlassen",
"sharing_silver_appbar_create_shared_album": "Neues geteiltes Album",
"sharing_silver_appbar_share_partner": "Teile mit Partner",
"share_add_photos": "Fotos hinzufügen",
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
"share_add": "Hinzufügen",
"create_shared_album_page_share_add_assets": "ELEMENTE HINZUFÜGEN",
"create_shared_album_page_share_select_photos": "Fotos auswählen",
"share_create_album": "Album erstellen",
"create_shared_album_page_share": "Teilen",
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
"share_invite": "Zum Album einladen",
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
"sharing_page_empty_list": "LEERE LISTE",
"sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.",
"sharing_page_album": "Geteilte Alben",
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
"exif_bottom_sheet_location": "STANDORT",
"exif_bottom_sheet_details": "DETAILS",
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
"backup_controller_page_server_storage": "Server Speicher",
"backup_controller_page_status_on": "Sicherung ist aktiv",
"backup_controller_page_status_off": "Sicherung ist inaktiv",
"backup_controller_page_turn_off": "Sicherung ausschalten",
"backup_controller_page_turn_on": "Sicherung einschalten",
"backup_controller_page_desc_backup": "Aktiviere die Sicherung um Elemente automatisch auf den Server zu laden.",
"backup_controller_page_backup_selected": "Ausgewählt: ",
"backup_all": "Alle",
"backup_controller_page_none_selected": "Keine ausgewählt",
"backup_controller_page_excluded": "Ausgeschlossen: ",
"backup_controller_page_albums": "Gesicherte Alben",
"backup_controller_page_to_backup": "Zu sichernde Alben",
"backup_controller_page_select": "Auswählen",
"backup_controller_page_backup": "Sicherung",
"backup_controller_page_info": "Informationen zur Sicherung",
"backup_controller_page_total": "Gesamt",
"backup_controller_page_total_sub": "Alle Fotos und Videos",
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
"backup_controller_page_remainder": "Übrig",
"backup_controller_page_remainder_sub": "Noch zu sichernde Fotos und Videos",
"backup_controller_page_cancel": "Abbrechen",
"backup_controller_page_start_backup": "Sicherung starten",
"album_info_card_backup_album_included": "EINGESCHLOSSEN",
"album_info_card_backup_album_excluded": "AUSGESCHLOSSEN",
"backup_info_card_assets": "Elemente",
"backup_album_selection_page_select_albums": "Alben auswählen",
"backup_album_selection_page_selection_info": "Auswahl",
"backup_album_selection_page_total_assets": "Elemente",
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
"backup_controller_page_storage_format": "{} von {} genutzt",
"tab_controller_nav_photos": "Fotos",
"tab_controller_nav_search": "Suche",
"tab_controller_nav_sharing": "Teilen",
"control_bottom_app_bar_delete": "Löschen",
"delete_dialog_title": "Für immer löschen",
"delete_dialog_alert": "Diese Elemente werden unwiderruflich von Immich und dem Gerät entfernt",
"delete_dialog_cancel": "Abbrechen",
"delete_dialog_ok": "Löschen",
"profile_drawer_sign_out": "Abmelden",
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
"search_bar_hint": "Durchsuche deine Fotos",
"search_page_places": "Orte",
"search_page_things": "Dinge",
"search_result_page_new_search_hint": "Neue Suche",
"search_page_no_places": "Keine Informationen über Orte verfügbar",
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89",
"version_announcement_overlay_text_1": "Hallo mein Freund! Es gibt eine neue Version von",
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
"version_announcement_overlay_release_notes": "Änderungsprotokoll",
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
"version_announcement_overlay_ack": "Ich habe verstanden"
}

View File

@ -0,0 +1,98 @@
{
"date_format": "E, LLL d, y \u2022 h:mm a",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy",
"monthly_title_text_date_format": "MMMM y",
"login_form_button_text": "Login",
"login_form_save_login": "Stay logged in",
"login_form_endpoint_url": "Server Endpoint URL",
"login_form_endpoint_hint": "http://your-server-ip:port/api",
"login_form_err_trailing_whitespace": "Trailing whitespace",
"login_form_err_leading_whitespace": "Leading whitespace",
"login_form_err_invalid_email": "Invalid Email",
"login_form_err_http": "Please specify http:// or https://",
"login_form_label_email": "Email",
"login_form_email_hint": "youremail@email.com",
"login_form_label_password": "Password",
"login_form_password_hint": "password",
"share_add_title": "Add a title",
"album_viewer_appbar_share_err_delete": "Failed to delete album",
"album_viewer_appbar_share_err_leave": "Failed to leave album",
"album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
"album_viewer_appbar_share_err_title": "Failed to change album title",
"album_viewer_appbar_share_remove": "Remove from album",
"album_viewer_appbar_share_delete": "Delete album",
"album_viewer_appbar_share_leave": "Leave album",
"sharing_silver_appbar_create_shared_album": "Create shared album",
"sharing_silver_appbar_share_partner": "Share with partner",
"share_add_photos": "Add photos",
"album_viewer_page_share_add_users": "Add users",
"share_add": "Add",
"create_shared_album_page_share_add_assets": "ADD ASSETS",
"create_shared_album_page_share_select_photos": "Select Photos",
"share_create_album": "Create album",
"create_shared_album_page_share": "Share",
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
"share_invite": "Invite to album",
"select_user_for_sharing_page_err_album": "Failed to create album",
"sharing_page_empty_list": "EMPTY LIST",
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
"sharing_page_album": "Shared albums",
"exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_details": "DETAILS",
"backup_err_only_album": "Cannot remove the only album",
"backup_controller_page_server_storage": "Server Storage",
"backup_controller_page_status_on": "Backup is on",
"backup_controller_page_status_off": "Backup is off",
"backup_controller_page_turn_off": "Turn off Backup",
"backup_controller_page_turn_on": "Turn on Backup",
"backup_controller_page_desc_backup": "Turn on backup to automatically upload new assets to the server.",
"backup_controller_page_backup_selected": "Selected: ",
"backup_all": "All",
"backup_controller_page_none_selected": "None selected",
"backup_controller_page_excluded": "Excluded: ",
"backup_controller_page_albums": "Backup Albums",
"backup_controller_page_to_backup": "Albums to be backup",
"backup_controller_page_select": "Select",
"backup_controller_page_backup": "Backup",
"backup_controller_page_info": "Backup Information",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
"backup_controller_page_backup_sub": "Backed up photos and videos",
"backup_controller_page_remainder": "Remainder",
"backup_controller_page_remainder_sub": "Remaining photos and albums to back up from selection",
"backup_controller_page_cancel": "Cancel",
"backup_controller_page_start_backup": "Start Backup",
"album_info_card_backup_album_included": "INCLUDED",
"album_info_card_backup_album_excluded": "EXCLUDED",
"backup_info_card_assets": "assets",
"backup_album_selection_page_select_albums": "Select Albums",
"backup_album_selection_page_selection_info": "Selection Info",
"backup_album_selection_page_total_assets": "Total unique assets",
"backup_album_selection_page_albums_device": "Albums on device ({})",
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_controller_page_storage_format": "{} of {} used",
"tab_controller_nav_photos": "Photos",
"tab_controller_nav_search": "Search",
"tab_controller_nav_sharing": "Sharing",
"control_bottom_app_bar_delete": "Delete",
"delete_dialog_title": "Delete Permanently",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_cancel": "Cancel",
"delete_dialog_ok": "Delete",
"profile_drawer_sign_out": "Sign Out",
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
"search_bar_hint": "Search your photos",
"search_page_places": "Places",
"search_page_things": "Things",
"search_result_page_new_search_hint": "New Search",
"search_page_no_places": "No Places Info Available",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_ack": "Acknowledge"
}

View File

@ -82,5 +82,11 @@
<array> <array>
<string>https</string> <string>https</string>
</array> </array>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
@ -36,7 +37,21 @@ void main() async {
), ),
); );
runApp(const ProviderScope(child: ImmichApp())); await EasyLocalization.ensureInitialized();
var locales = const [
// Default locale
Locale('en', 'US'),
// Additional locales
Locale('de', 'DE')
];
runApp(EasyLocalization(
supportedLocales: locales,
path: 'assets/i18n',
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ProviderScope(child: ImmichApp())));
} }
class ImmichApp extends ConsumerStatefulWidget { class ImmichApp extends ConsumerStatefulWidget {
@ -112,6 +127,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
return MaterialApp( return MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: Stack( home: Stack(
children: [ children: [

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -72,7 +73,7 @@ class ExifBottomSheet extends ConsumerWidget {
children: [ children: [
if (assetDetail.exifInfo?.dateTimeOriginal != null) if (assetDetail.exifInfo?.dateTimeOriginal != null)
Text( Text(
DateFormat('E, LLL d, y • h:mm a').format( DateFormat('date_format'.tr()).format(
DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!), DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
), ),
style: TextStyle( style: TextStyle(
@ -84,12 +85,12 @@ class ExifBottomSheet extends ConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(top: 16.0), padding: const EdgeInsets.only(top: 16.0),
child: Text( child: Text(
"Add Description...", "exif_bottom_sheet_description",
style: TextStyle( style: TextStyle(
color: Colors.grey[500], color: Colors.grey[500],
fontSize: 11, fontSize: 11,
), ),
), ).tr(),
), ),
// Location // Location
@ -104,9 +105,9 @@ class ExifBottomSheet extends ConsumerWidget {
color: Colors.grey[600], color: Colors.grey[600],
), ),
Text( Text(
"LOCATION", "exif_bottom_sheet_location",
style: TextStyle(fontSize: 11, color: Colors.grey[400]), style: TextStyle(fontSize: 11, color: Colors.grey[400]),
), ).tr(),
if (assetDetail.exifInfo?.latitude != null && if (assetDetail.exifInfo?.latitude != null &&
assetDetail.exifInfo?.longitude != null) assetDetail.exifInfo?.longitude != null)
_buildMap(), _buildMap(),
@ -134,9 +135,9 @@ class ExifBottomSheet extends ConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 8.0),
child: Text( child: Text(
"DETAILS", "exif_bottom_sheet_details",
style: TextStyle(fontSize: 11, color: Colors.grey[400]), style: TextStyle(fontSize: 11, color: Colors.grey[400]),
), ).tr(),
), ),
ListTile( ListTile(
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),

View File

@ -1,6 +1,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
@ -37,10 +38,10 @@ class AlbumInfoCard extends HookConsumerWidget {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
label: const Text( label: const Text(
"INCLUDED", "album_info_card_backup_album_included",
style: TextStyle( style: TextStyle(
fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold), fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
), ).tr(),
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
); );
} else if (isExcluded) { } else if (isExcluded) {
@ -48,10 +49,10 @@ class AlbumInfoCard extends HookConsumerWidget {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
label: const Text( label: const Text(
"EXCLUDED", "album_info_card_backup_album_excluded",
style: TextStyle( style: TextStyle(
fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold), fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
), ).tr(),
backgroundColor: Colors.red[300], backgroundColor: Colors.red[300],
); );
} }
@ -77,7 +78,7 @@ class AlbumInfoCard extends HookConsumerWidget {
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Cannot remove the only album", msg: "backup_err_only_album".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -104,7 +105,7 @@ class AlbumInfoCard extends HookConsumerWidget {
.contains(albumInfo)) { .contains(albumInfo)) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Cannot exclude the only album", msg: "backup_err_only_album".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -180,7 +181,10 @@ class AlbumInfoCard extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(top: 2.0), padding: const EdgeInsets.only(top: 2.0),
child: Text( child: Text(
'${albumInfo.assetCount} ${(albumInfo.isAll ? " (ALL)" : "")}', albumInfo.assetCount.toString() +
(albumInfo.isAll
? " (${'backup_all'.tr()})"
: ""),
style: TextStyle( style: TextStyle(
fontSize: 12, color: Colors.grey[600]), fontSize: 12, color: Colors.grey[600]),
), ),

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BackupInfoCard extends StatelessWidget { class BackupInfoCard extends StatelessWidget {
@ -44,7 +45,7 @@ class BackupInfoCard extends StatelessWidget {
info, info,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
), ),
const Text("assets"), const Text("backup_info_card_assets").tr(),
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
@ -55,7 +56,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Cannot remove the only album", msg: "backup_err_only_album".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -136,20 +137,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
title: const Text( title: const Text(
"Select Albums", "backup_album_selection_page_select_albums",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ).tr(),
elevation: 0, elevation: 0,
), ),
body: ListView( body: ListView(
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), padding:
child: Text( const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
"Selection Info", child: const Text(
"backup_album_selection_page_selection_info",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
), ).tr(),
), ),
// Selected Album Chips // Selected Album Chips
@ -181,14 +183,18 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
ListTile( ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
title: Text( title: Text(
"Total unique assets", "backup_album_selection_page_total_assets",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, fontSize: 14,
color: Colors.grey[700]), color: Colors.grey[700]),
), ).tr(),
trailing: Text( trailing: Text(
'${ref.watch(backupProvider).allUniqueAssets.length}', ref
.watch(backupProvider)
.allUniqueAssets
.length
.toString(),
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
@ -199,19 +205,20 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
ListTile( ListTile(
title: Text( title: Text(
"Albums on device (${availableAlbums.length})", "backup_album_selection_page_albums_device"
.tr(args: [availableAlbums.length.toString()]),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
), ),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
"Tap to include, double tap to exclude", "backup_album_selection_page_albums_tap",
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ).tr(),
), ),
trailing: IconButton( trailing: IconButton(
splashRadius: 16, splashRadius: 16,
@ -230,21 +237,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
elevation: 5, elevation: 5,
title: Text( title: Text(
'Selection Info', 'backup_album_selection_page_selection_info',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
), ),
), ).tr(),
content: SingleChildScrollView( content: SingleChildScrollView(
child: ListBody( child: ListBody(
children: [ children: [
Text( Text(
'Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.', 'backup_album_selection_page_assets_scatter',
style: TextStyle( style: TextStyle(
fontSize: 14, color: Colors.grey[700]), fontSize: 14, color: Colors.grey[700]),
), ).tr(),
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -44,9 +45,9 @@ class BackupControllerPage extends HookConsumerWidget {
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
), ),
title: const Text( title: const Text(
"Server storage", "backup_controller_page_server_storage",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
), ).tr(),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
@ -66,8 +67,11 @@ class BackupControllerPage extends HookConsumerWidget {
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 12.0), padding: const EdgeInsets.only(top: 12.0),
child: Text( child: const Text('backup_controller_page_storage_format').tr(
'${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'), args: [
backupState.serverInfo.diskUse,
backupState.serverInfo.diskSize
]),
), ),
], ],
), ),
@ -76,11 +80,13 @@ class BackupControllerPage extends HookConsumerWidget {
} }
ListTile _buildBackupController() { ListTile _buildBackupController() {
var backUpOption = var backUpOption = authenticationState.deviceInfo.isAutoBackup
authenticationState.deviceInfo.isAutoBackup ? "on" : "off"; ? "backup_controller_page_status_on".tr()
: "backup_controller_page_status_off".tr();
var isAutoBackup = authenticationState.deviceInfo.isAutoBackup; var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
var backupBtnText = var backupBtnText = authenticationState.deviceInfo.isAutoBackup
authenticationState.deviceInfo.isAutoBackup ? "off" : "on"; ? "backup_controller_page_turn_off".tr()
: "backup_controller_page_turn_on".tr();
return ListTile( return ListTile(
isThreeLine: true, isThreeLine: true,
leading: isAutoBackup leading: isAutoBackup
@ -90,7 +96,7 @@ class BackupControllerPage extends HookConsumerWidget {
) )
: const Icon(Icons.cloud_off_rounded), : const Icon(Icons.cloud_off_rounded),
title: Text( title: Text(
"Back up is $backUpOption", backUpOption,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
), ),
subtitle: Padding( subtitle: Padding(
@ -100,9 +106,9 @@ class BackupControllerPage extends HookConsumerWidget {
children: [ children: [
if (!isAutoBackup) if (!isAutoBackup)
const Text( const Text(
"Turn on backup to automatically upload new assets to the server.", "backup_controller_page_desc_backup",
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
), ).tr(),
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: OutlinedButton( child: OutlinedButton(
@ -123,7 +129,7 @@ class BackupControllerPage extends HookConsumerWidget {
.setAutoBackup(true); .setAutoBackup(true);
} }
}, },
child: Text("Turn $backupBtnText Backup", child: Text(backupBtnText,
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
), ),
) )
@ -134,13 +140,13 @@ class BackupControllerPage extends HookConsumerWidget {
} }
Widget _buildSelectedAlbumName() { Widget _buildSelectedAlbumName() {
var text = "Selected: "; var text = "backup_controller_page_backup_selected".tr();
var albums = ref.watch(backupProvider).selectedBackupAlbums; var albums = ref.watch(backupProvider).selectedBackupAlbums;
if (albums.isNotEmpty) { if (albums.isNotEmpty) {
for (var album in albums) { for (var album in albums) {
if (album.name == "Recent" || album.name == "Recents") { if (album.name == "Recent" || album.name == "Recents") {
text += "${album.name} (All), "; text += "${album.name} (${'backup_all'.tr()}), ";
} else { } else {
text += "${album.name}, "; text += "${album.name}, ";
} }
@ -160,7 +166,7 @@ class BackupControllerPage extends HookConsumerWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
"None selected", "backup_controller_page_none_selected".tr(),
style: TextStyle( style: TextStyle(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontSize: 12, fontSize: 12,
@ -171,7 +177,7 @@ class BackupControllerPage extends HookConsumerWidget {
} }
Widget _buildExcludedAlbumName() { Widget _buildExcludedAlbumName() {
var text = "Excluded: "; var text = "backup_controller_page_excluded".tr();
var albums = ref.watch(backupProvider).excludedBackupAlbums; var albums = ref.watch(backupProvider).excludedBackupAlbums;
if (albums.isNotEmpty) { if (albums.isNotEmpty) {
@ -207,17 +213,18 @@ class BackupControllerPage extends HookConsumerWidget {
borderOnForeground: false, borderOnForeground: false,
child: ListTile( child: ListTile(
minVerticalPadding: 15, minVerticalPadding: 15,
title: const Text("Backup Albums", title: const Text("backup_controller_page_albums",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20))
.tr(),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
"Albums to be backed up", "backup_controller_page_to_backup",
style: TextStyle(color: Color(0xFF808080), fontSize: 12), style: TextStyle(color: Color(0xFF808080), fontSize: 12),
), ).tr(),
_buildSelectedAlbumName(), _buildSelectedAlbumName(),
_buildExcludedAlbumName() _buildExcludedAlbumName()
], ],
@ -234,14 +241,14 @@ class BackupControllerPage extends HookConsumerWidget {
onPressed: () { onPressed: () {
AutoRouter.of(context).push(const BackupAlbumSelectionRoute()); AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
}, },
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 16.0, vertical: 16.0,
), ),
child: Text( child: const Text(
"Select", "backup_controller_page_select",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
), ),
), ),
), ),
@ -387,9 +394,9 @@ class BackupControllerPage extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
title: const Text( title: const Text(
"Backup", "backup_controller_page_backup",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ).tr(),
leading: IconButton( leading: IconButton(
onPressed: () { onPressed: () {
ref.watch(websocketProvider.notifier).listenUploadEvent(); ref.watch(websocketProvider.notifier).listenUploadEvent();
@ -405,27 +412,27 @@ class BackupControllerPage extends HookConsumerWidget {
child: ListView( child: ListView(
// crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: const Text(
"Backup Information", "backup_controller_page_info",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
), ).tr(),
), ),
_buildFolderSelectionTile(), _buildFolderSelectionTile(),
BackupInfoCard( BackupInfoCard(
title: "Total", title: "backup_controller_page_total".tr(),
subtitle: "All unique photos and videos from selected albums", subtitle: "backup_controller_page_total_sub".tr(),
info: "${backupState.allUniqueAssets.length}", info: "${backupState.allUniqueAssets.length}",
), ),
BackupInfoCard( BackupInfoCard(
title: "Backup", title: "backup_controller_page_backup".tr(),
subtitle: "Backed up photos and videos", subtitle: "backup_controller_page_backup_sub".tr(),
info: "${backupState.selectedAlbumsBackupAssetsIds.length}", info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
), ),
BackupInfoCard( BackupInfoCard(
title: "Remainder", title: "backup_controller_page_remainder".tr(),
subtitle: "Remaining photos and albums to back up from selection", subtitle: "backup_controller_page_remainder_sub".tr(),
info: info:
"${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
), ),
@ -452,12 +459,12 @@ class BackupControllerPage extends HookConsumerWidget {
ref.read(backupProvider.notifier).cancelBackup(); ref.read(backupProvider.notifier).cancelBackup();
}, },
child: const Text( child: const Text(
"CANCEL", "backup_controller_page_cancel",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ).tr(),
) )
: ElevatedButton( : ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@ -467,12 +474,12 @@ class BackupControllerPage extends HookConsumerWidget {
), ),
onPressed: shouldBackup ? startBackup : null, onPressed: shouldBackup ? startBackup : null,
child: const Text( child: const Text(
"START BACKUP", "backup_controller_page_start_backup",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ).tr(),
), ),
), ),
) )

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart'; import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
@ -26,7 +27,7 @@ class ControlBottomAppBar extends StatelessWidget {
children: [ children: [
ControlBoxButton( ControlBoxButton(
iconData: Icons.delete_forever_rounded, iconData: Icons.delete_forever_rounded,
label: "Delete", label: "control_bottom_app_bar_delete".tr(),
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
@ -19,7 +20,7 @@ class DailyTitleText extends ConsumerWidget {
var currentYear = DateTime.now().year; var currentYear = DateTime.now().year;
var groupYear = DateTime.parse(isoDate).year; var groupYear = DateTime.parse(isoDate).year;
var formatDateTemplate = var formatDateTemplate =
currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy'; currentYear == groupYear ? "daily_title_text_date".tr() : "daily_title_text_date_year".tr();
var dateText = var dateText =
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate)); DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
var isMultiSelectEnable = var isMultiSelectEnable =

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart';
@ -13,18 +14,17 @@ class DeleteDialog extends ConsumerWidget {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.grey[200], backgroundColor: Colors.grey[200],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: const Text("Delete Permanently"), title: const Text("delete_dialog_title").tr(),
content: const Text( content: const Text("delete_dialog_alert").tr(),
"These items will be permanently deleted from Immich and from your device"),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text( child: const Text(
"Cancel", "delete_dialog_cancel",
style: TextStyle(color: Colors.blueGrey), style: TextStyle(color: Colors.blueGrey),
), ).tr(),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -36,9 +36,9 @@ class DeleteDialog extends ConsumerWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
"Delete", "delete_dialog_ok",
style: TextStyle(color: Colors.red[400]), style: TextStyle(color: Colors.red[400]),
), ).tr(),
), ),
], ],
); );

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -11,7 +12,7 @@ class MonthlyTitleText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var monthTitleText = DateFormat('MMMM y').format(DateTime.parse(isoDate)); var monthTitleText = DateFormat("monthly_title_text_date_format".tr()).format(DateTime.parse(isoDate));
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Padding( child: Padding(

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
@ -183,12 +184,12 @@ class ProfileDrawer extends HookConsumerWidget {
color: Colors.black54, color: Colors.black54,
), ),
title: const Text( title: const Text(
"Sign Out", "profile_drawer_sign_out",
style: TextStyle( style: TextStyle(
color: Colors.black54, color: Colors.black54,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ).tr(),
onTap: () async { onTap: () async {
bool res = bool res =
await ref.watch(authenticationProvider.notifier).logout(); await ref.watch(authenticationProvider.notifier).logout();
@ -227,7 +228,7 @@ class ProfileDrawer extends HookConsumerWidget {
child: Text( child: Text(
serverInfoState.isVersionMismatch serverInfoState.isVersionMismatch
? serverInfoState.versionMismatchErrorMessage ? serverInfoState.versionMismatchErrorMessage
: "Client and Server are up-to-date", : "profile_drawer_client_server_up_to_date".tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -21,7 +22,7 @@ class LoginForm extends HookConsumerWidget {
final passwordController = final passwordController =
useTextEditingController.fromValue(TextEditingValue.empty); useTextEditingController.fromValue(TextEditingValue.empty);
final serverEndpointController = final serverEndpointController =
useTextEditingController(text: 'http://your-server-ip:2283/api'); useTextEditingController(text: 'login_endpoint_hint'.tr());
final isSaveLoginInfo = useState<bool>(false); final isSaveLoginInfo = useState<bool>(false);
useEffect(() { useEffect(() {
@ -73,12 +74,12 @@ class LoginForm extends HookConsumerWidget {
borderRadius: BorderRadius.circular(5)), borderRadius: BorderRadius.circular(5)),
enableFeedback: true, enableFeedback: true,
title: const Text( title: const Text(
"Stay logged in", "login_form_save_login",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.grey), color: Colors.grey),
), ).tr(),
value: isSaveLoginInfo.value, value: isSaveLoginInfo.value,
onChanged: (switchValue) { onChanged: (switchValue) {
if (switchValue != null) { if (switchValue != null) {
@ -107,11 +108,11 @@ class ServerEndpointInput extends StatelessWidget {
: super(key: key); : super(key: key);
String? _validateInput(String? url) { String? _validateInput(String? url) {
if (url?.startsWith(RegExp(r'https?://')) == true) { if (url?.startsWith(RegExp(r'https?://')) == true) {
return null; return null;
} else { } else {
return 'Please specify http:// or https://'; return 'login_form_err_http'.tr();
} }
} }
@ -119,10 +120,10 @@ class ServerEndpointInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Server Endpoint URL', labelText: 'login_form_endpoint_url'.tr(),
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'http://your-server-ip:port', hintText: 'login_form_endpoint_hint'.tr(),
), ),
validator: _validateInput, validator: _validateInput,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
@ -137,9 +138,10 @@ class EmailInput extends StatelessWidget {
String? _validateInput(String? email) { String? _validateInput(String? email) {
if (email == null || email == '') return null; if (email == null || email == '') return null;
if (email.endsWith(' ')) return 'Trailing whitespace'; if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
if (email.startsWith(' ')) return 'Leading whitespace'; if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
if (email.contains(' ') || !email.contains('@')) return 'Invalid Email'; if (email.contains(' ') || !email.contains('@'))
return 'login_form_err_invalid_email'.tr();
return null; return null;
} }
@ -147,10 +149,10 @@ class EmailInput extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Email', labelText: 'login_form_label_email'.tr(),
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'youremail@email.com', hintText: 'login_form_email_hint'.tr(),
), ),
validator: _validateInput, validator: _validateInput,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
@ -168,10 +170,10 @@ class PasswordInput extends StatelessWidget {
return TextFormField( return TextFormField(
obscureText: true, obscureText: true,
controller: controller, controller: controller,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'login_form_label_password'.tr(),
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'password'), hintText: 'login_form_password_hint'.tr()),
); );
} }
} }
@ -222,15 +224,14 @@ class LoginButton extends ConsumerWidget {
} else { } else {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: msg: "login_failed".tr(),
"Error logging you in, check server url, email and password!",
toastType: ToastType.error, toastType: ToastType.error,
); );
} }
}, },
child: const Text( child: const Text(
"Login", "login_form_button_text",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
)); ).tr());
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -47,8 +48,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
onChanged: (value) { onChanged: (value) {
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value); ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
}, },
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Search your photos', hintText: 'search_bar_hint'.tr(),
enabledBorder: UnderlineInputBorder( enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
), ),

View File

@ -55,7 +55,7 @@ class ThumbnailWithInfo extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width / 3, width: MediaQuery.of(context).size.width / 3,
child: Text( child: Text(
textInfo.capitalizeFirstLetter(), textInfo,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
@ -82,7 +83,7 @@ class SearchPage extends HookConsumerWidget {
return ThumbnailWithInfo( return ThumbnailWithInfo(
imageUrl: imageUrl:
'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60', 'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
textInfo: 'No Places Info Available', textInfo: 'search_page_no_places'.tr(),
onTap: () {}, onTap: () {},
); );
}), }),
@ -134,7 +135,7 @@ class SearchPage extends HookConsumerWidget {
return ThumbnailWithInfo( return ThumbnailWithInfo(
imageUrl: imageUrl:
'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60', 'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
textInfo: 'No Object Info Available', textInfo: 'sarch_no_objects'.tr(),
onTap: () {}, onTap: () {},
); );
}), }),
@ -158,20 +159,20 @@ class SearchPage extends HookConsumerWidget {
children: [ children: [
ListView( ListView(
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text( child: const Text(
"Places", "search_page_places",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
), ).tr(),
), ),
_buildPlaces(), _buildPlaces(),
const Padding( Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text( child: const Text(
"Things", "search_page_things",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
), ).tr(),
), ),
_buildThings() _buildThings()
], ],

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -66,8 +67,8 @@ class SearchResultPage extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value); ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
}, },
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'New Search', hintText: 'search_result_page_new_search_hint'.tr(),
enabledBorder: UnderlineInputBorder( enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
), ),

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart'; import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
@ -59,7 +60,7 @@ class AlbumTitleTextField extends ConsumerWidget {
borderSide: const BorderSide(color: Colors.transparent), borderSide: const BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
hintText: 'Add a title', hintText: 'share_add_title'.tr(),
focusColor: Colors.grey[300], focusColor: Colors.grey[300],
fillColor: Colors.grey[200], fillColor: Colors.grey[200],
filled: isAlbumTitleTextFieldFocus.value, filled: isAlbumTitleTextFieldFocus.value,

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -45,7 +46,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
} else { } else {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Failed to delete album", msg: "album_viewer_appbar_share_err_delete".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -67,7 +68,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
Navigator.pop(context); Navigator.pop(context);
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Failed to leave album", msg: "album_viewer_appbar_share_err_leave".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -93,7 +94,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
Navigator.pop(context); Navigator.pop(context);
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "There are problems in removing assets from album", msg: "album_viewer_appbar_share_err_remove".tr(),
toastType: ToastType.error, toastType: ToastType.error,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
@ -108,9 +109,9 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
return ListTile( return ListTile(
leading: const Icon(Icons.delete_sweep_rounded), leading: const Icon(Icons.delete_sweep_rounded),
title: const Text( title: const Text(
'Remove from album', 'album_viewer_appbar_share_remove',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
onTap: () => _onRemoveFromAlbumPressed(albumId), onTap: () => _onRemoveFromAlbumPressed(albumId),
); );
} else { } else {
@ -121,18 +122,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
return ListTile( return ListTile(
leading: const Icon(Icons.delete_forever_rounded), leading: const Icon(Icons.delete_forever_rounded),
title: const Text( title: const Text(
'Delete album', 'album_viewer_appbar_share_delete',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
onTap: () => _onDeleteAlbumPressed(albumId), onTap: () => _onDeleteAlbumPressed(albumId),
); );
} else { } else {
return ListTile( return ListTile(
leading: const Icon(Icons.person_remove_rounded), leading: const Icon(Icons.person_remove_rounded),
title: const Text( title: const Text(
'Leave album', 'album_viewer_appbar_share_leave',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
onTap: () => _onLeaveAlbumPressed(albumId), onTap: () => _onLeaveAlbumPressed(albumId),
); );
} }
@ -176,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
if (!isSuccess) { if (!isSuccess) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: "Failed to change album title", msg: "album_viewer_appbar_share_err_title".tr(),
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
toastType: ToastType.error, toastType: ToastType.error,
); );

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -74,7 +75,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
focusColor: Colors.grey[300], focusColor: Colors.grey[300],
fillColor: Colors.grey[200], fillColor: Colors.grey[200],
filled: titleFocusNode.hasFocus, filled: titleFocusNode.hasFocus,
hintText: 'Add a title', hintText: 'share_add_title'.tr(),
), ),
); );
} }

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
@ -51,10 +52,10 @@ class SharingSliverAppBar extends StatelessWidget {
size: 20, size: 20,
), ),
label: const Text( label: const Text(
"Create shared album", "sharing_silver_appbar_create_shared_album",
style: style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 12), TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
), ).tr(),
), ),
), ),
), ),
@ -73,10 +74,10 @@ class SharingSliverAppBar extends StatelessWidget {
size: 20, size: 20,
), ),
label: const Text( label: const Text(
"Share with partner", "sharing_silver_appbar_share_partner",
style: style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 12), TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
), ).tr(),
), ),
), ),
) )

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -204,13 +205,13 @@ class AlbumViewerPage extends HookConsumerWidget {
AlbumActionOutlinedButton( AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined, iconData: Icons.add_photo_alternate_outlined,
onPressed: () => _onAddPhotosPressed(albumInfo), onPressed: () => _onAddPhotosPressed(albumInfo),
labelText: "Add photos", labelText: "share_add_photos".tr(),
), ),
if (userId == albumInfo.ownerId) if (userId == albumInfo.ownerId)
AlbumActionOutlinedButton( AlbumActionOutlinedButton(
iconData: Icons.person_add_alt_rounded, iconData: Icons.person_add_alt_rounded,
onPressed: () => _onAddUsersPressed(albumInfo), onPressed: () => _onAddUsersPressed(albumInfo),
labelText: "Add users", labelText: "album_viewer_page_share_add_users".tr(),
), ),
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -65,9 +66,9 @@ class AssetSelectionPage extends HookConsumerWidget {
), ),
title: selectedAssets.isEmpty title: selectedAssets.isEmpty
? const Text( ? const Text(
'Add photos', 'share_add_photos',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
) ).tr()
: Text( : Text(
_buildAssetCountText(), _buildAssetCountText(),
style: const TextStyle(fontSize: 18), style: const TextStyle(fontSize: 18),
@ -86,9 +87,9 @@ class AssetSelectionPage extends HookConsumerWidget {
AutoRouter.of(context).pop(payload); AutoRouter.of(context).pop(payload);
}, },
child: const Text( child: const Text(
"Add", "share_add",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
), ),
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -64,13 +65,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
_buildTitle() { _buildTitle() {
if (selectedAssets.isEmpty) { if (selectedAssets.isEmpty) {
return const SliverToBoxAdapter( return SliverToBoxAdapter(
child: Padding( child: Padding(
padding: EdgeInsets.only(top: 200, left: 18), padding: EdgeInsets.only(top: 200, left: 18),
child: Text( child: Text(
'ADD ASSETS', 'create_shared_album_page_share_add_assets',
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
), ).tr(),
), ),
); );
} }
@ -97,12 +98,12 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
label: Padding( label: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
'Select Photos', 'create_shared_album_page_share_select_photos',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Colors.grey[700], color: Colors.grey[700],
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ).tr(),
), ),
), ),
), ),
@ -123,7 +124,7 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
AlbumActionOutlinedButton( AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined, iconData: Icons.add_photo_alternate_outlined,
onPressed: _onSelectPhotosButtonPressed, onPressed: _onSelectPhotosButtonPressed,
labelText: "Add photos", labelText: "share_add_photos".tr(),
), ),
], ],
), ),
@ -169,16 +170,16 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
}, },
icon: const Icon(Icons.close_rounded)), icon: const Icon(Icons.close_rounded)),
title: const Text( title: const Text(
'Create album', 'share_create_album',
style: TextStyle(color: Colors.black), style: TextStyle(color: Colors.black),
), ).tr(),
actions: [ actions: [
TextButton( TextButton(
onPressed: albumTitleController.text.isNotEmpty onPressed: albumTitleController.text.isNotEmpty
? _showSelectUserPage ? _showSelectUserPage
: null, : null,
child: const Text( child: Text(
'Share', 'create_shared_album_page_share'.tr(),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -68,10 +69,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
Wrap( Wrap(
children: [...usersChip], children: [...usersChip],
), ),
const Padding( Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text( child: Text(
'Suggestions', 'select_additional_user_for_sharing_page_suggestions'.tr(),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey, color: Colors.grey,
@ -112,9 +113,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text( title: const Text(
'Invite to album', 'share_invite',
style: TextStyle(color: Colors.black), style: TextStyle(color: Colors.black),
), ).tr(),
elevation: 0, elevation: 0,
centerTitle: false, centerTitle: false,
leading: IconButton( leading: IconButton(
@ -128,9 +129,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
onPressed: onPressed:
sharedUsersList.value.isEmpty ? null : _addNewUsersHandler, sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
child: const Text( child: const Text(
"Add", "share_add",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
), ).tr(),
) )
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -36,8 +37,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
.navigate(const TabControllerRoute(children: [SharingRoute()])); .navigate(const TabControllerRoute(children: [SharingRoute()]));
} }
const ScaffoldMessenger( ScaffoldMessenger(child: SnackBar(content: Text('select_user_for_sharing_page_err_album').tr()));
child: SnackBar(content: Text('Failed to create album')));
} }
_buildTileIcon(User user) { _buildTileIcon(User user) {
@ -84,15 +84,15 @@ class SelectUserForSharingPage extends HookConsumerWidget {
Wrap( Wrap(
children: [...usersChip], children: [...usersChip],
), ),
const Padding( Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text( child: Text(
'Suggestions', 'share_suggestions',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey, color: Colors.grey,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ).tr(),
), ),
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
@ -128,9 +128,9 @@ class SelectUserForSharingPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text( title: const Text(
'Invite to album', 'share_invite',
style: TextStyle(color: Colors.black), style: TextStyle(color: Colors.black),
), ).tr(),
elevation: 0, elevation: 0,
centerTitle: false, centerTitle: false,
leading: IconButton( leading: IconButton(
@ -144,9 +144,9 @@ class SelectUserForSharingPage extends HookConsumerWidget {
onPressed: onPressed:
sharedUsersList.value.isEmpty ? null : _createSharedAlbum, sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
child: const Text( child: const Text(
"Create Album", "share_create_album",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
)) ).tr())
], ],
), ),
body: suggestedShareUsers.when( body: suggestedShareUsers.when(

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -104,20 +105,20 @@ class SharingPage extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'EMPTY LIST', 'sharing_page_empty_list',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ).tr(),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'Create shared albums to share photos and videos with people in your network.', 'sharing_page_description',
style: TextStyle(fontSize: 12, color: Colors.grey[700]), style: TextStyle(fontSize: 12, color: Colors.grey[700]),
), ).tr(),
), ),
], ],
), ),
@ -131,15 +132,15 @@ class SharingPage extends HookConsumerWidget {
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [
const SharingSliverAppBar(), const SharingSliverAppBar(),
const SliverPadding( SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: Text( child: Text(
"Shared albums", "sharing_page_album",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ).tr(),
), ),
), ),
sharedAlbums.isNotEmpty sharedAlbums.isNotEmpty

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
@ -41,13 +42,16 @@ class TabControllerPage extends ConsumerWidget {
onTap: (index) { onTap: (index) {
tabsRouter.setActiveIndex(index); tabsRouter.setActiveIndex(index);
}, },
items: const [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
label: 'Photos', icon: Icon(Icons.photo)), label: 'tab_controller_nav_photos'.tr(),
icon: const Icon(Icons.photo)),
BottomNavigationBarItem( BottomNavigationBarItem(
label: 'Search', icon: Icon(Icons.search)), label: 'tab_controller_nav_search'.tr(),
icon: const Icon(Icons.search)),
BottomNavigationBarItem( BottomNavigationBarItem(
label: 'Sharing', icon: Icon(Icons.group_outlined)), label: 'tab_controller_nav_sharing'.tr(),
icon: const Icon(Icons.group_outlined)),
], ],
), ),
), ),

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -40,14 +41,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
"New Server Version Available 🎉", "version_announcement_overlay_title",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.indigo, color: Colors.indigo,
), ),
), ).tr(),
Padding( Padding(
padding: const EdgeInsets.only(top: 16.0), padding: const EdgeInsets.only(top: 16.0),
child: RichText( child: RichText(
@ -58,9 +59,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
color: Colors.black87, color: Colors.black87,
height: 1.2), height: 1.2),
children: <TextSpan>[ children: <TextSpan>[
const TextSpan( TextSpan(
text: text: 'version_announcement_overlay_text_1'.tr(),
'Hi friend, there is a new release of',
), ),
const TextSpan( const TextSpan(
text: ' Immich ', text: ' Immich ',
@ -70,22 +70,21 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const TextSpan( TextSpan(
text: text: "version_announcement_overlay_text_2".tr(),
"please take your time to visit the ",
), ),
TextSpan( TextSpan(
text: "release note", text: "version_announcement_overlay_release_notes"
.tr(),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = goToReleaseNote, ..onTap = goToReleaseNote,
), ),
const TextSpan( TextSpan(
text: text: "version_announcement_overlay_text_3".tr(),
" and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", )
),
], ],
), ),
), ),
@ -93,24 +92,23 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(top: 16.0), padding: const EdgeInsets.only(top: 16.0),
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shape: const StadiumBorder(), shape: const StadiumBorder(),
visualDensity: VisualDensity.standard, visualDensity: VisualDensity.standard,
primary: Colors.indigo, primary: Colors.indigo,
onPrimary: Colors.grey[50], onPrimary: Colors.grey[50],
elevation: 2, elevation: 2,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 25), vertical: 10, horizontal: 25),
),
onPressed: onAcknowledgeTapped,
child: const Text(
"Acknowledge",
style: TextStyle(
fontSize: 14,
), ),
), onPressed: onAcknowledgeTapped,
), child: const Text(
), "version_announcement_overlay_ack",
style: TextStyle(
fontSize: 14,
),
).tr()),
)
], ],
), ),
), ),

View File

@ -253,6 +253,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.6" version: "4.0.6"
easy_localization:
dependency: "direct main"
description:
name: easy_localization
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
easy_logger:
dependency: transitive
description:
name: easy_logger
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@ -335,6 +349,11 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_map: flutter_map:
dependency: "direct main" dependency: "direct main"
description: description:
@ -842,6 +861,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.27.3" version: "0.27.3"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.15"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.12"
shared_preferences_ios:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:

View File

@ -40,6 +40,7 @@ dependencies:
url_launcher: ^6.1.3 url_launcher: ^6.1.3
http: 0.13.4 http: 0.13.4
cancellation_token_http: ^1.1.0 cancellation_token_http: ^1.1.0
easy_localization: ^3.0.1
path: ^1.8.1 path: ^1.8.1
path_provider: ^2.0.11 path_provider: ^2.0.11
@ -59,6 +60,7 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/ - assets/
- assets/i18n/
fonts: fonts:
- family: WorkSans - family: WorkSans
fonts: fonts:

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
import json
import subprocess
def main():
with open('assets/i18n/en-US.json', 'r') as f:
data = json.load(f)
for k in data.keys():
print(k)
sp = subprocess.run(['sh', '-c', f'grep -r --include="*.dart" "{k}"'])
if sp.returncode != 0:
print("Not found in source code!")
return 1
if __name__ == '__main__':
main()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
import json
import subprocess
def main():
print("CHECK GERMAN TRANSLATIONS")
with open('assets/i18n/de-DE.json', 'r') as f:
data = json.load(f)
for k in data.keys():
print(k)
sp = subprocess.run(['sh', '-c', f'grep -r --include="./assets/i18n/en-US.json" "{k}"'])
if sp.returncode != 0:
print(f"Outdated Key! {k}")
return 1
if __name__ == '__main__':
main()