mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										15
									
								
								localizely.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								localizely.yml
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										98
									
								
								mobile/assets/i18n/de-DE.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/assets/i18n/de-DE.json
									
									
									
									
									
										Normal 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" | ||||
| } | ||||
							
								
								
									
										98
									
								
								mobile/assets/i18n/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/assets/i18n/en-US.json
									
									
									
									
									
										Normal 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" | ||||
| } | ||||
| @@ -82,5 +82,11 @@ | ||||
|     <array> | ||||
|       <string>https</string> | ||||
|     </array> | ||||
|  | ||||
|     <key>CFBundleLocalizations</key> | ||||
|     <array> | ||||
|       <string>en</string> | ||||
|       <string>de</string> | ||||
|     </array> | ||||
|   </dict> | ||||
| </plist> | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.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 { | ||||
| @@ -112,6 +127,9 @@ class ImmichAppState extends ConsumerState<ImmichApp> | ||||
|     ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); | ||||
|  | ||||
|     return MaterialApp( | ||||
|       localizationsDelegates: context.localizationDelegates, | ||||
|       supportedLocales: context.supportedLocales, | ||||
|       locale: context.locale, | ||||
|       debugShowCheckedModeBanner: false, | ||||
|       home: Stack( | ||||
|         children: [ | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_map/flutter_map.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -72,7 +73,7 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|         children: [ | ||||
|           if (assetDetail.exifInfo?.dateTimeOriginal != null) | ||||
|             Text( | ||||
|               DateFormat('E, LLL d, y • h:mm a').format( | ||||
|               DateFormat('date_format'.tr()).format( | ||||
|                 DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!), | ||||
|               ), | ||||
|               style: TextStyle( | ||||
| @@ -84,12 +85,12 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|           Padding( | ||||
|             padding: const EdgeInsets.only(top: 16.0), | ||||
|             child: Text( | ||||
|               "Add Description...", | ||||
|               "exif_bottom_sheet_description", | ||||
|               style: TextStyle( | ||||
|                 color: Colors.grey[500], | ||||
|                 fontSize: 11, | ||||
|               ), | ||||
|             ), | ||||
|             ).tr(), | ||||
|           ), | ||||
|  | ||||
|           // Location | ||||
| @@ -104,9 +105,9 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                     color: Colors.grey[600], | ||||
|                   ), | ||||
|                   Text( | ||||
|                     "LOCATION", | ||||
|                     "exif_bottom_sheet_location", | ||||
|                     style: TextStyle(fontSize: 11, color: Colors.grey[400]), | ||||
|                   ), | ||||
|                   ).tr(), | ||||
|                   if (assetDetail.exifInfo?.latitude != null && | ||||
|                       assetDetail.exifInfo?.longitude != null) | ||||
|                     _buildMap(), | ||||
| @@ -134,9 +135,9 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.only(bottom: 8.0), | ||||
|                     child: Text( | ||||
|                       "DETAILS", | ||||
|                       "exif_bottom_sheet_details", | ||||
|                       style: TextStyle(fontSize: 11, color: Colors.grey[400]), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                   ), | ||||
|                   ListTile( | ||||
|                     contentPadding: const EdgeInsets.all(0), | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'dart:typed_data'; | ||||
|  | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:fluttertoast/fluttertoast.dart'; | ||||
| @@ -37,10 +38,10 @@ class AlbumInfoCard extends HookConsumerWidget { | ||||
|           visualDensity: VisualDensity.compact, | ||||
|           shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), | ||||
|           label: const Text( | ||||
|             "INCLUDED", | ||||
|             "album_info_card_backup_album_included", | ||||
|             style: TextStyle( | ||||
|                 fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|           ).tr(), | ||||
|           backgroundColor: Theme.of(context).primaryColor, | ||||
|         ); | ||||
|       } else if (isExcluded) { | ||||
| @@ -48,10 +49,10 @@ class AlbumInfoCard extends HookConsumerWidget { | ||||
|           visualDensity: VisualDensity.compact, | ||||
|           shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), | ||||
|           label: const Text( | ||||
|             "EXCLUDED", | ||||
|             "album_info_card_backup_album_excluded", | ||||
|             style: TextStyle( | ||||
|                 fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|           ).tr(), | ||||
|           backgroundColor: Colors.red[300], | ||||
|         ); | ||||
|       } | ||||
| @@ -77,7 +78,7 @@ class AlbumInfoCard extends HookConsumerWidget { | ||||
|           if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { | ||||
|             ImmichToast.show( | ||||
|               context: context, | ||||
|               msg: "Cannot remove the only album", | ||||
|               msg: "backup_err_only_album".tr(), | ||||
|               toastType: ToastType.error, | ||||
|               gravity: ToastGravity.BOTTOM, | ||||
|             ); | ||||
| @@ -104,7 +105,7 @@ class AlbumInfoCard extends HookConsumerWidget { | ||||
|                   .contains(albumInfo)) { | ||||
|             ImmichToast.show( | ||||
|               context: context, | ||||
|               msg: "Cannot exclude the only album", | ||||
|               msg: "backup_err_only_album".tr(), | ||||
|               toastType: ToastType.error, | ||||
|               gravity: ToastGravity.BOTTOM, | ||||
|             ); | ||||
| @@ -180,7 +181,10 @@ class AlbumInfoCard extends HookConsumerWidget { | ||||
|                           Padding( | ||||
|                             padding: const EdgeInsets.only(top: 2.0), | ||||
|                             child: Text( | ||||
|                               '${albumInfo.assetCount} ${(albumInfo.isAll ? " (ALL)" : "")}', | ||||
|                               albumInfo.assetCount.toString() + | ||||
|                                   (albumInfo.isAll | ||||
|                                       ? " (${'backup_all'.tr()})" | ||||
|                                       : ""), | ||||
|                               style: TextStyle( | ||||
|                                   fontSize: 12, color: Colors.grey[600]), | ||||
|                             ), | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class BackupInfoCard extends StatelessWidget { | ||||
| @@ -44,7 +45,7 @@ class BackupInfoCard extends StatelessWidget { | ||||
|               info, | ||||
|               style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             const Text("assets"), | ||||
|             const Text("backup_info_card_assets").tr(), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:fluttertoast/fluttertoast.dart'; | ||||
| @@ -55,7 +56,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { | ||||
|           if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { | ||||
|             ImmichToast.show( | ||||
|               context: context, | ||||
|               msg: "Cannot remove the only album", | ||||
|               msg: "backup_err_only_album".tr(), | ||||
|               toastType: ToastType.error, | ||||
|               gravity: ToastGravity.BOTTOM, | ||||
|             ); | ||||
| @@ -136,20 +137,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { | ||||
|           icon: const Icon(Icons.arrow_back_ios_rounded), | ||||
|         ), | ||||
|         title: const Text( | ||||
|           "Select Albums", | ||||
|           "backup_album_selection_page_select_albums", | ||||
|           style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), | ||||
|         ), | ||||
|         ).tr(), | ||||
|         elevation: 0, | ||||
|       ), | ||||
|       body: ListView( | ||||
|         physics: const ClampingScrollPhysics(), | ||||
|         children: [ | ||||
|           const Padding( | ||||
|             padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), | ||||
|             child: Text( | ||||
|               "Selection Info", | ||||
|           Padding( | ||||
|             padding: | ||||
|                 const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), | ||||
|             child: const Text( | ||||
|               "backup_album_selection_page_selection_info", | ||||
|               style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), | ||||
|             ), | ||||
|             ).tr(), | ||||
|           ), | ||||
|           // Selected Album Chips | ||||
|  | ||||
| @@ -181,14 +183,18 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { | ||||
|                   ListTile( | ||||
|                     visualDensity: VisualDensity.compact, | ||||
|                     title: Text( | ||||
|                       "Total unique assets", | ||||
|                       "backup_album_selection_page_total_assets", | ||||
|                       style: TextStyle( | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                           fontSize: 14, | ||||
|                           color: Colors.grey[700]), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                     trailing: Text( | ||||
|                       '${ref.watch(backupProvider).allUniqueAssets.length}', | ||||
|                       ref | ||||
|                           .watch(backupProvider) | ||||
|                           .allUniqueAssets | ||||
|                           .length | ||||
|                           .toString(), | ||||
|                       style: const TextStyle(fontWeight: FontWeight.bold), | ||||
|                     ), | ||||
|                   ), | ||||
| @@ -199,19 +205,20 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { | ||||
|  | ||||
|           ListTile( | ||||
|             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), | ||||
|             ), | ||||
|             subtitle: Padding( | ||||
|               padding: const EdgeInsets.symmetric(vertical: 8.0), | ||||
|               child: Text( | ||||
|                 "Tap to include, double tap to exclude", | ||||
|                 "backup_album_selection_page_albums_tap", | ||||
|                 style: TextStyle( | ||||
|                   fontSize: 12, | ||||
|                   color: Theme.of(context).primaryColor, | ||||
|                   fontWeight: FontWeight.bold, | ||||
|                 ), | ||||
|               ), | ||||
|               ).tr(), | ||||
|             ), | ||||
|             trailing: IconButton( | ||||
|               splashRadius: 16, | ||||
| @@ -230,21 +237,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { | ||||
|                           borderRadius: BorderRadius.circular(12)), | ||||
|                       elevation: 5, | ||||
|                       title: Text( | ||||
|                         'Selection Info', | ||||
|                         'backup_album_selection_page_selection_info', | ||||
|                         style: TextStyle( | ||||
|                           fontSize: 16, | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                           color: Theme.of(context).primaryColor, | ||||
|                         ), | ||||
|                       ), | ||||
|                       ).tr(), | ||||
|                       content: SingleChildScrollView( | ||||
|                         child: ListBody( | ||||
|                           children: [ | ||||
|                             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( | ||||
|                                   fontSize: 14, color: Colors.grey[700]), | ||||
|                             ), | ||||
|                             ).tr(), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -44,9 +45,9 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|           color: Theme.of(context).primaryColor, | ||||
|         ), | ||||
|         title: const Text( | ||||
|           "Server storage", | ||||
|           "backup_controller_page_server_storage", | ||||
|           style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), | ||||
|         ), | ||||
|         ).tr(), | ||||
|         subtitle: Padding( | ||||
|           padding: const EdgeInsets.only(top: 8.0), | ||||
|           child: Column( | ||||
| @@ -66,8 +67,11 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 12.0), | ||||
|                 child: Text( | ||||
|                     '${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'), | ||||
|                 child: const Text('backup_controller_page_storage_format').tr( | ||||
|                     args: [ | ||||
|                       backupState.serverInfo.diskUse, | ||||
|                       backupState.serverInfo.diskSize | ||||
|                     ]), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
| @@ -76,11 +80,13 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     ListTile _buildBackupController() { | ||||
|       var backUpOption = | ||||
|           authenticationState.deviceInfo.isAutoBackup ? "on" : "off"; | ||||
|       var backUpOption = authenticationState.deviceInfo.isAutoBackup | ||||
|           ? "backup_controller_page_status_on".tr() | ||||
|           : "backup_controller_page_status_off".tr(); | ||||
|       var isAutoBackup = authenticationState.deviceInfo.isAutoBackup; | ||||
|       var backupBtnText = | ||||
|           authenticationState.deviceInfo.isAutoBackup ? "off" : "on"; | ||||
|       var backupBtnText = authenticationState.deviceInfo.isAutoBackup | ||||
|           ? "backup_controller_page_turn_off".tr() | ||||
|           : "backup_controller_page_turn_on".tr(); | ||||
|       return ListTile( | ||||
|         isThreeLine: true, | ||||
|         leading: isAutoBackup | ||||
| @@ -90,7 +96,7 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|               ) | ||||
|             : const Icon(Icons.cloud_off_rounded), | ||||
|         title: Text( | ||||
|           "Back up is $backUpOption", | ||||
|           backUpOption, | ||||
|           style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), | ||||
|         ), | ||||
|         subtitle: Padding( | ||||
| @@ -100,9 +106,9 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|             children: [ | ||||
|               if (!isAutoBackup) | ||||
|                 const Text( | ||||
|                   "Turn on backup to automatically upload new assets to the server.", | ||||
|                   "backup_controller_page_desc_backup", | ||||
|                   style: TextStyle(fontSize: 14), | ||||
|                 ), | ||||
|                 ).tr(), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 8.0), | ||||
|                 child: OutlinedButton( | ||||
| @@ -123,7 +129,7 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|                           .setAutoBackup(true); | ||||
|                     } | ||||
|                   }, | ||||
|                   child: Text("Turn $backupBtnText Backup", | ||||
|                   child: Text(backupBtnText, | ||||
|                       style: const TextStyle(fontWeight: FontWeight.bold)), | ||||
|                 ), | ||||
|               ) | ||||
| @@ -134,13 +140,13 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     Widget _buildSelectedAlbumName() { | ||||
|       var text = "Selected: "; | ||||
|       var text = "backup_controller_page_backup_selected".tr(); | ||||
|       var albums = ref.watch(backupProvider).selectedBackupAlbums; | ||||
|  | ||||
|       if (albums.isNotEmpty) { | ||||
|         for (var album in albums) { | ||||
|           if (album.name == "Recent" || album.name == "Recents") { | ||||
|             text += "${album.name} (All), "; | ||||
|             text += "${album.name} (${'backup_all'.tr()}), "; | ||||
|           } else { | ||||
|             text += "${album.name}, "; | ||||
|           } | ||||
| @@ -160,7 +166,7 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|         return Padding( | ||||
|           padding: const EdgeInsets.only(top: 8.0), | ||||
|           child: Text( | ||||
|             "None selected", | ||||
|             "backup_controller_page_none_selected".tr(), | ||||
|             style: TextStyle( | ||||
|                 color: Theme.of(context).primaryColor, | ||||
|                 fontSize: 12, | ||||
| @@ -171,7 +177,7 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     Widget _buildExcludedAlbumName() { | ||||
|       var text = "Excluded: "; | ||||
|       var text = "backup_controller_page_excluded".tr(); | ||||
|       var albums = ref.watch(backupProvider).excludedBackupAlbums; | ||||
|  | ||||
|       if (albums.isNotEmpty) { | ||||
| @@ -207,17 +213,18 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|         borderOnForeground: false, | ||||
|         child: ListTile( | ||||
|           minVerticalPadding: 15, | ||||
|           title: const Text("Backup Albums", | ||||
|               style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), | ||||
|           title: const Text("backup_controller_page_albums", | ||||
|                   style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)) | ||||
|               .tr(), | ||||
|           subtitle: Padding( | ||||
|             padding: const EdgeInsets.only(top: 8.0), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 const Text( | ||||
|                   "Albums to be backed up", | ||||
|                   "backup_controller_page_to_backup", | ||||
|                   style: TextStyle(color: Color(0xFF808080), fontSize: 12), | ||||
|                 ), | ||||
|                 ).tr(), | ||||
|                 _buildSelectedAlbumName(), | ||||
|                 _buildExcludedAlbumName() | ||||
|               ], | ||||
| @@ -234,14 +241,14 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|             onPressed: () { | ||||
|               AutoRouter.of(context).push(const BackupAlbumSelectionRoute()); | ||||
|             }, | ||||
|             child: const Padding( | ||||
|               padding: EdgeInsets.symmetric( | ||||
|             child: Padding( | ||||
|               padding: const EdgeInsets.symmetric( | ||||
|                 vertical: 16.0, | ||||
|               ), | ||||
|               child: Text( | ||||
|                 "Select", | ||||
|               child: const Text( | ||||
|                 "backup_controller_page_select", | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               ).tr(), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
| @@ -387,9 +394,9 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|       appBar: AppBar( | ||||
|         elevation: 0, | ||||
|         title: const Text( | ||||
|           "Backup", | ||||
|           "backup_controller_page_backup", | ||||
|           style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), | ||||
|         ), | ||||
|         ).tr(), | ||||
|         leading: IconButton( | ||||
|             onPressed: () { | ||||
|               ref.watch(websocketProvider.notifier).listenUploadEvent(); | ||||
| @@ -405,27 +412,27 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|         child: ListView( | ||||
|           // crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             const Padding( | ||||
|               padding: EdgeInsets.all(8.0), | ||||
|               child: Text( | ||||
|                 "Backup Information", | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.all(8.0), | ||||
|               child: const Text( | ||||
|                 "backup_controller_page_info", | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), | ||||
|               ), | ||||
|               ).tr(), | ||||
|             ), | ||||
|             _buildFolderSelectionTile(), | ||||
|             BackupInfoCard( | ||||
|               title: "Total", | ||||
|               subtitle: "All unique photos and videos from selected albums", | ||||
|               title: "backup_controller_page_total".tr(), | ||||
|               subtitle: "backup_controller_page_total_sub".tr(), | ||||
|               info: "${backupState.allUniqueAssets.length}", | ||||
|             ), | ||||
|             BackupInfoCard( | ||||
|               title: "Backup", | ||||
|               subtitle: "Backed up photos and videos", | ||||
|               title: "backup_controller_page_backup".tr(), | ||||
|               subtitle: "backup_controller_page_backup_sub".tr(), | ||||
|               info: "${backupState.selectedAlbumsBackupAssetsIds.length}", | ||||
|             ), | ||||
|             BackupInfoCard( | ||||
|               title: "Remainder", | ||||
|               subtitle: "Remaining photos and albums to back up from selection", | ||||
|               title: "backup_controller_page_remainder".tr(), | ||||
|               subtitle: "backup_controller_page_remainder_sub".tr(), | ||||
|               info: | ||||
|                   "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", | ||||
|             ), | ||||
| @@ -452,12 +459,12 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|                               ref.read(backupProvider.notifier).cancelBackup(); | ||||
|                             }, | ||||
|                             child: const Text( | ||||
|                               "CANCEL", | ||||
|                               "backup_controller_page_cancel", | ||||
|                               style: TextStyle( | ||||
|                                 fontSize: 14, | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                               ), | ||||
|                             ), | ||||
|                             ).tr(), | ||||
|                           ) | ||||
|                         : ElevatedButton( | ||||
|                             style: ElevatedButton.styleFrom( | ||||
| @@ -467,12 +474,12 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|                             ), | ||||
|                             onPressed: shouldBackup ? startBackup : null, | ||||
|                             child: const Text( | ||||
|                               "START BACKUP", | ||||
|                               "backup_controller_page_start_backup", | ||||
|                               style: TextStyle( | ||||
|                                 fontSize: 14, | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                               ), | ||||
|                             ), | ||||
|                             ).tr(), | ||||
|                           ), | ||||
|               ), | ||||
|             ) | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart'; | ||||
|  | ||||
| @@ -26,7 +27,7 @@ class ControlBottomAppBar extends StatelessWidget { | ||||
|                 children: [ | ||||
|                   ControlBoxButton( | ||||
|                     iconData: Icons.delete_forever_rounded, | ||||
|                     label: "Delete", | ||||
|                     label: "control_bottom_app_bar_delete".tr(), | ||||
|                     onPressed: () { | ||||
|                       showDialog( | ||||
|                         context: context, | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.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 groupYear = DateTime.parse(isoDate).year; | ||||
|     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 = | ||||
|         DateFormat(formatDateTemplate).format(DateTime.parse(isoDate)); | ||||
|     var isMultiSelectEnable = | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||
| @@ -13,18 +14,17 @@ class DeleteDialog extends ConsumerWidget { | ||||
|     return AlertDialog( | ||||
|       backgroundColor: Colors.grey[200], | ||||
|       shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), | ||||
|       title: const Text("Delete Permanently"), | ||||
|       content: const Text( | ||||
|           "These items will be permanently deleted from Immich and from your device"), | ||||
|       title: const Text("delete_dialog_title").tr(), | ||||
|       content: const Text("delete_dialog_alert").tr(), | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|           onPressed: () { | ||||
|             Navigator.of(context).pop(); | ||||
|           }, | ||||
|           child: const Text( | ||||
|             "Cancel", | ||||
|             "delete_dialog_cancel", | ||||
|             style: TextStyle(color: Colors.blueGrey), | ||||
|           ), | ||||
|           ).tr(), | ||||
|         ), | ||||
|         TextButton( | ||||
|           onPressed: () { | ||||
| @@ -36,9 +36,9 @@ class DeleteDialog extends ConsumerWidget { | ||||
|             Navigator.of(context).pop(); | ||||
|           }, | ||||
|           child: Text( | ||||
|             "Delete", | ||||
|             "delete_dialog_ok", | ||||
|             style: TextStyle(color: Colors.red[400]), | ||||
|           ), | ||||
|           ).tr(), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:intl/intl.dart'; | ||||
|  | ||||
| @@ -11,7 +12,7 @@ class MonthlyTitleText extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   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( | ||||
|       child: Padding( | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hive_flutter/hive_flutter.dart'; | ||||
| @@ -183,12 +184,12 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                   color: Colors.black54, | ||||
|                 ), | ||||
|                 title: const Text( | ||||
|                   "Sign Out", | ||||
|                   "profile_drawer_sign_out", | ||||
|                   style: TextStyle( | ||||
|                       color: Colors.black54, | ||||
|                       fontSize: 14, | ||||
|                       fontWeight: FontWeight.bold), | ||||
|                 ), | ||||
|                 ).tr(), | ||||
|                 onTap: () async { | ||||
|                   bool res = | ||||
|                       await ref.watch(authenticationProvider.notifier).logout(); | ||||
| @@ -227,7 +228,7 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                       child: Text( | ||||
|                         serverInfoState.isVersionMismatch | ||||
|                             ? serverInfoState.versionMismatchErrorMessage | ||||
|                             : "Client and Server are up-to-date", | ||||
|                             : "profile_drawer_client_server_up_to_date".tr(), | ||||
|                         textAlign: TextAlign.center, | ||||
|                         style: TextStyle( | ||||
|                             fontSize: 11, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| @@ -21,7 +22,7 @@ class LoginForm extends HookConsumerWidget { | ||||
|     final passwordController = | ||||
|         useTextEditingController.fromValue(TextEditingValue.empty); | ||||
|     final serverEndpointController = | ||||
|         useTextEditingController(text: 'http://your-server-ip:2283/api'); | ||||
|         useTextEditingController(text: 'login_endpoint_hint'.tr()); | ||||
|     final isSaveLoginInfo = useState<bool>(false); | ||||
|  | ||||
|     useEffect(() { | ||||
| @@ -73,12 +74,12 @@ class LoginForm extends HookConsumerWidget { | ||||
|                     borderRadius: BorderRadius.circular(5)), | ||||
|                 enableFeedback: true, | ||||
|                 title: const Text( | ||||
|                   "Stay logged in", | ||||
|                   "login_form_save_login", | ||||
|                   style: TextStyle( | ||||
|                       fontSize: 16, | ||||
|                       fontWeight: FontWeight.bold, | ||||
|                       color: Colors.grey), | ||||
|                 ), | ||||
|                 ).tr(), | ||||
|                 value: isSaveLoginInfo.value, | ||||
|                 onChanged: (switchValue) { | ||||
|                   if (switchValue != null) { | ||||
| @@ -107,11 +108,11 @@ class ServerEndpointInput extends StatelessWidget { | ||||
|       : super(key: key); | ||||
|  | ||||
|   String? _validateInput(String? url) { | ||||
|    | ||||
|  | ||||
|     if (url?.startsWith(RegExp(r'https?://')) == true) { | ||||
|       return null; | ||||
|     } 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) { | ||||
|     return TextFormField( | ||||
|       controller: controller, | ||||
|       decoration: const InputDecoration( | ||||
|         labelText: 'Server Endpoint URL', | ||||
|       decoration: InputDecoration( | ||||
|         labelText: 'login_form_endpoint_url'.tr(), | ||||
|         border: OutlineInputBorder(), | ||||
|         hintText: 'http://your-server-ip:port', | ||||
|         hintText: 'login_form_endpoint_hint'.tr(), | ||||
|       ), | ||||
|       validator: _validateInput, | ||||
|       autovalidateMode: AutovalidateMode.always, | ||||
| @@ -137,9 +138,10 @@ class EmailInput extends StatelessWidget { | ||||
|  | ||||
|   String? _validateInput(String? email) { | ||||
|     if (email == null || email == '') return null; | ||||
|     if (email.endsWith(' ')) return 'Trailing whitespace'; | ||||
|     if (email.startsWith(' ')) return 'Leading whitespace'; | ||||
|     if (email.contains(' ') || !email.contains('@')) return 'Invalid Email'; | ||||
|     if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr(); | ||||
|     if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr(); | ||||
|     if (email.contains(' ') || !email.contains('@')) | ||||
|       return 'login_form_err_invalid_email'.tr(); | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
| @@ -147,10 +149,10 @@ class EmailInput extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     return TextFormField( | ||||
|       controller: controller, | ||||
|       decoration: const InputDecoration( | ||||
|         labelText: 'Email', | ||||
|       decoration: InputDecoration( | ||||
|         labelText: 'login_form_label_email'.tr(), | ||||
|         border: OutlineInputBorder(), | ||||
|         hintText: 'youremail@email.com', | ||||
|         hintText: 'login_form_email_hint'.tr(), | ||||
|       ), | ||||
|       validator: _validateInput, | ||||
|       autovalidateMode: AutovalidateMode.always, | ||||
| @@ -168,10 +170,10 @@ class PasswordInput extends StatelessWidget { | ||||
|     return TextFormField( | ||||
|       obscureText: true, | ||||
|       controller: controller, | ||||
|       decoration: const InputDecoration( | ||||
|           labelText: 'Password', | ||||
|       decoration: InputDecoration( | ||||
|           labelText: 'login_form_label_password'.tr(), | ||||
|           border: OutlineInputBorder(), | ||||
|           hintText: 'password'), | ||||
|           hintText: 'login_form_password_hint'.tr()), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -222,15 +224,14 @@ class LoginButton extends ConsumerWidget { | ||||
|           } else { | ||||
|             ImmichToast.show( | ||||
|               context: context, | ||||
|               msg: | ||||
|                   "Error logging you in, check server url, email and password!", | ||||
|               msg: "login_failed".tr(), | ||||
|               toastType: ToastType.error, | ||||
|             ); | ||||
|           } | ||||
|         }, | ||||
|         child: const Text( | ||||
|           "Login", | ||||
|           "login_form_button_text", | ||||
|           style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), | ||||
|         )); | ||||
|         ).tr()); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -47,8 +48,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|         onChanged: (value) { | ||||
|           ref.watch(searchPageStateProvider.notifier).setSearchTerm(value); | ||||
|         }, | ||||
|         decoration: const InputDecoration( | ||||
|           hintText: 'Search your photos', | ||||
|         decoration: InputDecoration( | ||||
|           hintText: 'search_bar_hint'.tr(), | ||||
|           enabledBorder: UnderlineInputBorder( | ||||
|             borderSide: BorderSide(color: Colors.transparent), | ||||
|           ), | ||||
|   | ||||
| @@ -55,7 +55,7 @@ class ThumbnailWithInfo extends StatelessWidget { | ||||
|                 child: SizedBox( | ||||
|                   width: MediaQuery.of(context).size.width / 3, | ||||
|                   child: Text( | ||||
|                     textInfo.capitalizeFirstLetter(), | ||||
|                     textInfo, | ||||
|                     style: const TextStyle( | ||||
|                       color: Colors.white, | ||||
|                       fontWeight: FontWeight.bold, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hive_flutter/hive_flutter.dart'; | ||||
| @@ -82,7 +83,7 @@ class SearchPage extends HookConsumerWidget { | ||||
|                       return ThumbnailWithInfo( | ||||
|                         imageUrl: | ||||
|                             '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: () {}, | ||||
|                       ); | ||||
|                     }), | ||||
| @@ -134,7 +135,7 @@ class SearchPage extends HookConsumerWidget { | ||||
|                       return ThumbnailWithInfo( | ||||
|                         imageUrl: | ||||
|                             '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: () {}, | ||||
|                       ); | ||||
|                     }), | ||||
| @@ -158,20 +159,20 @@ class SearchPage extends HookConsumerWidget { | ||||
|           children: [ | ||||
|             ListView( | ||||
|               children: [ | ||||
|                 const Padding( | ||||
|                 Padding( | ||||
|                   padding: EdgeInsets.all(16.0), | ||||
|                   child: Text( | ||||
|                     "Places", | ||||
|                   child: const Text( | ||||
|                     "search_page_places", | ||||
|                     style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), | ||||
|                   ), | ||||
|                   ).tr(), | ||||
|                 ), | ||||
|                 _buildPlaces(), | ||||
|                 const Padding( | ||||
|                 Padding( | ||||
|                   padding: EdgeInsets.all(16.0), | ||||
|                   child: Text( | ||||
|                     "Things", | ||||
|                   child: const  Text( | ||||
|                     "search_page_things", | ||||
|                     style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), | ||||
|                   ), | ||||
|                   ).tr(), | ||||
|                 ), | ||||
|                 _buildThings() | ||||
|               ], | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_spinkit/flutter_spinkit.dart'; | ||||
| @@ -66,8 +67,8 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|         onChanged: (value) { | ||||
|           ref.watch(searchPageStateProvider.notifier).setSearchTerm(value); | ||||
|         }, | ||||
|         decoration: const InputDecoration( | ||||
|           hintText: 'New Search', | ||||
|         decoration: InputDecoration( | ||||
|           hintText: 'search_result_page_new_search_hint'.tr(), | ||||
|           enabledBorder: UnderlineInputBorder( | ||||
|             borderSide: BorderSide(color: Colors.transparent), | ||||
|           ), | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.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), | ||||
|           borderRadius: BorderRadius.circular(10), | ||||
|         ), | ||||
|         hintText: 'Add a title', | ||||
|         hintText: 'share_add_title'.tr(), | ||||
|         focusColor: Colors.grey[300], | ||||
|         fillColor: Colors.grey[200], | ||||
|         filled: isAlbumTitleTextFieldFocus.value, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:fluttertoast/fluttertoast.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -45,7 +46,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|       } else { | ||||
|         ImmichToast.show( | ||||
|           context: context, | ||||
|           msg: "Failed to delete album", | ||||
|           msg: "album_viewer_appbar_share_err_delete".tr(), | ||||
|           toastType: ToastType.error, | ||||
|           gravity: ToastGravity.BOTTOM, | ||||
|         ); | ||||
| @@ -67,7 +68,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|         Navigator.pop(context); | ||||
|         ImmichToast.show( | ||||
|           context: context, | ||||
|           msg: "Failed to leave album", | ||||
|           msg: "album_viewer_appbar_share_err_leave".tr(), | ||||
|           toastType: ToastType.error, | ||||
|           gravity: ToastGravity.BOTTOM, | ||||
|         ); | ||||
| @@ -93,7 +94,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|         Navigator.pop(context); | ||||
|         ImmichToast.show( | ||||
|           context: context, | ||||
|           msg: "There are problems in removing assets from album", | ||||
|           msg: "album_viewer_appbar_share_err_remove".tr(), | ||||
|           toastType: ToastType.error, | ||||
|           gravity: ToastGravity.BOTTOM, | ||||
|         ); | ||||
| @@ -108,9 +109,9 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|           return ListTile( | ||||
|             leading: const Icon(Icons.delete_sweep_rounded), | ||||
|             title: const Text( | ||||
|               'Remove from album', | ||||
|               'album_viewer_appbar_share_remove', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             ).tr(), | ||||
|             onTap: () => _onRemoveFromAlbumPressed(albumId), | ||||
|           ); | ||||
|         } else { | ||||
| @@ -121,18 +122,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|           return ListTile( | ||||
|             leading: const Icon(Icons.delete_forever_rounded), | ||||
|             title: const Text( | ||||
|               'Delete album', | ||||
|               'album_viewer_appbar_share_delete', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             ).tr(), | ||||
|             onTap: () => _onDeleteAlbumPressed(albumId), | ||||
|           ); | ||||
|         } else { | ||||
|           return ListTile( | ||||
|             leading: const Icon(Icons.person_remove_rounded), | ||||
|             title: const Text( | ||||
|               'Leave album', | ||||
|               'album_viewer_appbar_share_leave', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             ).tr(), | ||||
|             onTap: () => _onLeaveAlbumPressed(albumId), | ||||
|           ); | ||||
|         } | ||||
| @@ -176,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|             if (!isSuccess) { | ||||
|               ImmichToast.show( | ||||
|                 context: context, | ||||
|                 msg: "Failed to change album title", | ||||
|                 msg: "album_viewer_appbar_share_err_title".tr(), | ||||
|                 gravity: ToastGravity.BOTTOM, | ||||
|                 toastType: ToastType.error, | ||||
|               ); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -74,7 +75,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { | ||||
|         focusColor: Colors.grey[300], | ||||
|         fillColor: Colors.grey[200], | ||||
|         filled: titleFocusNode.hasFocus, | ||||
|         hintText: 'Add a title', | ||||
|         hintText: 'share_add_title'.tr(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
|  | ||||
| @@ -51,10 +52,10 @@ class SharingSliverAppBar extends StatelessWidget { | ||||
|                       size: 20, | ||||
|                     ), | ||||
|                     label: const Text( | ||||
|                       "Create shared album", | ||||
|                       "sharing_silver_appbar_create_shared_album", | ||||
|                       style: | ||||
|                           TextStyle(fontWeight: FontWeight.bold, fontSize: 12), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
| @@ -73,10 +74,10 @@ class SharingSliverAppBar extends StatelessWidget { | ||||
|                       size: 20, | ||||
|                     ), | ||||
|                     label: const Text( | ||||
|                       "Share with partner", | ||||
|                       "sharing_silver_appbar_share_partner", | ||||
|                       style: | ||||
|                           TextStyle(fontWeight: FontWeight.bold, fontSize: 12), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                   ), | ||||
|                 ), | ||||
|               ) | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -204,13 +205,13 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|               AlbumActionOutlinedButton( | ||||
|                 iconData: Icons.add_photo_alternate_outlined, | ||||
|                 onPressed: () => _onAddPhotosPressed(albumInfo), | ||||
|                 labelText: "Add photos", | ||||
|                 labelText: "share_add_photos".tr(), | ||||
|               ), | ||||
|               if (userId == albumInfo.ownerId) | ||||
|                 AlbumActionOutlinedButton( | ||||
|                   iconData: Icons.person_add_alt_rounded, | ||||
|                   onPressed: () => _onAddUsersPressed(albumInfo), | ||||
|                   labelText: "Add users", | ||||
|                   labelText: "album_viewer_page_share_add_users".tr(), | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -65,9 +66,9 @@ class AssetSelectionPage extends HookConsumerWidget { | ||||
|         ), | ||||
|         title: selectedAssets.isEmpty | ||||
|             ? const Text( | ||||
|                 'Add photos', | ||||
|                 'share_add_photos', | ||||
|                 style: TextStyle(fontSize: 18), | ||||
|               ) | ||||
|               ).tr() | ||||
|             : Text( | ||||
|                 _buildAssetCountText(), | ||||
|                 style: const TextStyle(fontSize: 18), | ||||
| @@ -86,9 +87,9 @@ class AssetSelectionPage extends HookConsumerWidget { | ||||
|                 AutoRouter.of(context).pop(payload); | ||||
|               }, | ||||
|               child: const Text( | ||||
|                 "Add", | ||||
|                 "share_add", | ||||
|                 style: TextStyle(fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               ).tr(), | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -64,13 +65,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|  | ||||
|     _buildTitle() { | ||||
|       if (selectedAssets.isEmpty) { | ||||
|         return const SliverToBoxAdapter( | ||||
|         return SliverToBoxAdapter( | ||||
|           child: Padding( | ||||
|             padding: EdgeInsets.only(top: 200, left: 18), | ||||
|             child: Text( | ||||
|               'ADD ASSETS', | ||||
|               'create_shared_album_page_share_add_assets', | ||||
|               style: TextStyle(fontSize: 12), | ||||
|             ), | ||||
|             ).tr(), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
| @@ -97,12 +98,12 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|               label: Padding( | ||||
|                 padding: const EdgeInsets.only(left: 8.0), | ||||
|                 child: Text( | ||||
|                   'Select Photos', | ||||
|                   'create_shared_album_page_share_select_photos', | ||||
|                   style: TextStyle( | ||||
|                       fontSize: 16, | ||||
|                       color: Colors.grey[700], | ||||
|                       fontWeight: FontWeight.bold), | ||||
|                 ), | ||||
|                 ).tr(), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
| @@ -123,7 +124,7 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|               AlbumActionOutlinedButton( | ||||
|                 iconData: Icons.add_photo_alternate_outlined, | ||||
|                 onPressed: _onSelectPhotosButtonPressed, | ||||
|                 labelText: "Add photos", | ||||
|                 labelText: "share_add_photos".tr(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
| @@ -169,16 +170,16 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|               }, | ||||
|               icon: const Icon(Icons.close_rounded)), | ||||
|           title: const Text( | ||||
|             'Create album', | ||||
|             'share_create_album', | ||||
|             style: TextStyle(color: Colors.black), | ||||
|           ), | ||||
|           ).tr(), | ||||
|           actions: [ | ||||
|             TextButton( | ||||
|               onPressed: albumTitleController.text.isNotEmpty | ||||
|                   ? _showSelectUserPage | ||||
|                   : null, | ||||
|               child: const Text( | ||||
|                 'Share', | ||||
|               child: Text( | ||||
|                 'create_shared_album_page_share'.tr(), | ||||
|                 style: TextStyle( | ||||
|                   fontWeight: FontWeight.bold, | ||||
|                 ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -68,10 +69,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|           Wrap( | ||||
|             children: [...usersChip], | ||||
|           ), | ||||
|           const Padding( | ||||
|           Padding( | ||||
|             padding: EdgeInsets.all(16.0), | ||||
|             child: Text( | ||||
|               'Suggestions', | ||||
|               'select_additional_user_for_sharing_page_suggestions'.tr(), | ||||
|               style: TextStyle( | ||||
|                   fontSize: 14, | ||||
|                   color: Colors.grey, | ||||
| @@ -112,9 +113,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: const Text( | ||||
|           'Invite to album', | ||||
|           'share_invite', | ||||
|           style: TextStyle(color: Colors.black), | ||||
|         ), | ||||
|         ).tr(), | ||||
|         elevation: 0, | ||||
|         centerTitle: false, | ||||
|         leading: IconButton( | ||||
| @@ -128,9 +129,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|             onPressed: | ||||
|                 sharedUsersList.value.isEmpty ? null : _addNewUsersHandler, | ||||
|             child: const Text( | ||||
|               "Add", | ||||
|               "share_add", | ||||
|               style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             ).tr(), | ||||
|           ) | ||||
|         ], | ||||
|       ), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -36,8 +37,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|             .navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|       } | ||||
|  | ||||
|       const ScaffoldMessenger( | ||||
|           child: SnackBar(content: Text('Failed to create album'))); | ||||
|       ScaffoldMessenger(child: SnackBar(content: Text('select_user_for_sharing_page_err_album').tr())); | ||||
|     } | ||||
|  | ||||
|     _buildTileIcon(User user) { | ||||
| @@ -84,15 +84,15 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|           Wrap( | ||||
|             children: [...usersChip], | ||||
|           ), | ||||
|           const Padding( | ||||
|           Padding( | ||||
|             padding: EdgeInsets.all(16.0), | ||||
|             child: Text( | ||||
|               'Suggestions', | ||||
|               'share_suggestions', | ||||
|               style: TextStyle( | ||||
|                   fontSize: 14, | ||||
|                   color: Colors.grey, | ||||
|                   fontWeight: FontWeight.bold), | ||||
|             ), | ||||
|             ).tr(), | ||||
|           ), | ||||
|           ListView.builder( | ||||
|             shrinkWrap: true, | ||||
| @@ -128,9 +128,9 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: const Text( | ||||
|           'Invite to album', | ||||
|           'share_invite', | ||||
|           style: TextStyle(color: Colors.black), | ||||
|         ), | ||||
|         ).tr(), | ||||
|         elevation: 0, | ||||
|         centerTitle: false, | ||||
|         leading: IconButton( | ||||
| @@ -144,9 +144,9 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|               onPressed: | ||||
|                   sharedUsersList.value.isEmpty ? null : _createSharedAlbum, | ||||
|               child: const Text( | ||||
|                 "Create Album", | ||||
|                 "share_create_album", | ||||
|                 style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), | ||||
|               )) | ||||
|               ).tr()) | ||||
|         ], | ||||
|       ), | ||||
|       body: suggestedShareUsers.when( | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| @@ -104,20 +105,20 @@ class SharingPage extends HookConsumerWidget { | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.all(8.0), | ||||
|                     child: Text( | ||||
|                       'EMPTY LIST', | ||||
|                       'sharing_page_empty_list', | ||||
|                       style: TextStyle( | ||||
|                         fontSize: 12, | ||||
|                         color: Theme.of(context).primaryColor, | ||||
|                         fontWeight: FontWeight.bold, | ||||
|                       ), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                   ), | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.all(8.0), | ||||
|                     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]), | ||||
|                     ), | ||||
|                     ).tr(), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
| @@ -131,15 +132,15 @@ class SharingPage extends HookConsumerWidget { | ||||
|       body: CustomScrollView( | ||||
|         slivers: [ | ||||
|           const SharingSliverAppBar(), | ||||
|           const SliverPadding( | ||||
|           SliverPadding( | ||||
|             padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), | ||||
|             sliver: SliverToBoxAdapter( | ||||
|               child: Text( | ||||
|                 "Shared albums", | ||||
|                 "sharing_page_album", | ||||
|                 style: TextStyle( | ||||
|                   fontWeight: FontWeight.bold, | ||||
|                 ), | ||||
|               ), | ||||
|               ).tr(), | ||||
|             ), | ||||
|           ), | ||||
|           sharedAlbums.isNotEmpty | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; | ||||
| @@ -41,13 +42,16 @@ class TabControllerPage extends ConsumerWidget { | ||||
|                     onTap: (index) { | ||||
|                       tabsRouter.setActiveIndex(index); | ||||
|                     }, | ||||
|                     items: const [ | ||||
|                     items: [ | ||||
|                       BottomNavigationBarItem( | ||||
|                           label: 'Photos', icon: Icon(Icons.photo)), | ||||
|                           label: 'tab_controller_nav_photos'.tr(), | ||||
|                           icon: const Icon(Icons.photo)), | ||||
|                       BottomNavigationBarItem( | ||||
|                           label: 'Search', icon: Icon(Icons.search)), | ||||
|                           label: 'tab_controller_nav_search'.tr(), | ||||
|                           icon: const Icon(Icons.search)), | ||||
|                       BottomNavigationBarItem( | ||||
|                           label: 'Sharing', icon: Icon(Icons.group_outlined)), | ||||
|                           label: 'tab_controller_nav_sharing'.tr(), | ||||
|                           icon: const Icon(Icons.group_outlined)), | ||||
|                     ], | ||||
|                   ), | ||||
|           ), | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -40,14 +41,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             const Text( | ||||
|                               "New Server Version Available 🎉", | ||||
|                               "version_announcement_overlay_title", | ||||
|                               style: TextStyle( | ||||
|                                 fontSize: 16, | ||||
|                                 fontFamily: 'WorkSans', | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                                 color: Colors.indigo, | ||||
|                               ), | ||||
|                             ), | ||||
|                             ).tr(), | ||||
|                             Padding( | ||||
|                               padding: const EdgeInsets.only(top: 16.0), | ||||
|                               child: RichText( | ||||
| @@ -58,9 +59,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                                       color: Colors.black87, | ||||
|                                       height: 1.2), | ||||
|                                   children: <TextSpan>[ | ||||
|                                     const TextSpan( | ||||
|                                       text: | ||||
|                                           'Hi friend, there is a new release of', | ||||
|                                     TextSpan( | ||||
|                                       text: 'version_announcement_overlay_text_1'.tr(), | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: ' Immich ', | ||||
| @@ -70,22 +70,21 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                                         fontWeight: FontWeight.bold, | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: | ||||
|                                           "please take your time to visit the ", | ||||
|                                     TextSpan( | ||||
|                                       text: "version_announcement_overlay_text_2".tr(), | ||||
|                                     ), | ||||
|                                     TextSpan( | ||||
|                                       text: "release note", | ||||
|                                       text: "version_announcement_overlay_release_notes" | ||||
|                                           .tr(), | ||||
|                                       style: const TextStyle( | ||||
|                                         decoration: TextDecoration.underline, | ||||
|                                       ), | ||||
|                                       recognizer: TapGestureRecognizer() | ||||
|                                         ..onTap = goToReleaseNote, | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: | ||||
|                                           " 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.", | ||||
|                                     ), | ||||
|                                     TextSpan( | ||||
|                                       text: "version_announcement_overlay_text_3".tr(), | ||||
|                                     ) | ||||
|                                   ], | ||||
|                                 ), | ||||
|                               ), | ||||
| @@ -93,24 +92,23 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                             Padding( | ||||
|                               padding: const EdgeInsets.only(top: 16.0), | ||||
|                               child: ElevatedButton( | ||||
|                                 style: ElevatedButton.styleFrom( | ||||
|                                   shape: const StadiumBorder(), | ||||
|                                   visualDensity: VisualDensity.standard, | ||||
|                                   primary: Colors.indigo, | ||||
|                                   onPrimary: Colors.grey[50], | ||||
|                                   elevation: 2, | ||||
|                                   padding: const EdgeInsets.symmetric( | ||||
|                                       vertical: 10, horizontal: 25), | ||||
|                                 ), | ||||
|                                 onPressed: onAcknowledgeTapped, | ||||
|                                 child: const Text( | ||||
|                                   "Acknowledge", | ||||
|                                   style: TextStyle( | ||||
|                                     fontSize: 14, | ||||
|                                   style: ElevatedButton.styleFrom( | ||||
|                                     shape: const StadiumBorder(), | ||||
|                                     visualDensity: VisualDensity.standard, | ||||
|                                     primary: Colors.indigo, | ||||
|                                     onPrimary: Colors.grey[50], | ||||
|                                     elevation: 2, | ||||
|                                     padding: const EdgeInsets.symmetric( | ||||
|                                         vertical: 10, horizontal: 25), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ), | ||||
|                                   onPressed: onAcknowledgeTapped, | ||||
|                                   child: const Text( | ||||
|                                     "version_announcement_overlay_ack", | ||||
|                                     style: TextStyle( | ||||
|                                       fontSize: 14, | ||||
|                                     ), | ||||
|                                   ).tr()), | ||||
|                             ) | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|   | ||||
| @@ -253,6 +253,20 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -335,6 +349,11 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   flutter_localizations: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   flutter_map: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -842,6 +861,62 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -40,6 +40,7 @@ dependencies: | ||||
|   url_launcher: ^6.1.3 | ||||
|   http: 0.13.4 | ||||
|   cancellation_token_http: ^1.1.0 | ||||
|   easy_localization: ^3.0.1 | ||||
|  | ||||
|   path: ^1.8.1 | ||||
|   path_provider: ^2.0.11 | ||||
| @@ -59,6 +60,7 @@ flutter: | ||||
|   uses-material-design: true | ||||
|   assets: | ||||
|     - assets/ | ||||
|     - assets/i18n/ | ||||
|   fonts: | ||||
|     - family: WorkSans | ||||
|       fonts: | ||||
|   | ||||
							
								
								
									
										18
									
								
								mobile/scripts/check_i18n_keys.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mobile/scripts/check_i18n_keys.py
									
									
									
									
									
										Normal 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() | ||||
							
								
								
									
										19
									
								
								mobile/scripts/check_key_uniform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								mobile/scripts/check_key_uniform.py
									
									
									
									
									
										Normal 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() | ||||
		Reference in New Issue
	
	Block a user