diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..5fc505f --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-class: AppLocalizations +output-localization-file: app_localizations.dart +nullable-getter: false +untranslated-messages-file: build/l10n_missing.txt diff --git a/lib/app.dart b/lib/app.dart index 8503abb..5ca1ad3 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart'; import 'ui/features/preferences/providers.dart'; @@ -18,13 +18,28 @@ class MyApp extends StatelessWidget { loading: () => const SizedBox.shrink(), error: (e, st) => MaterialApp( - home: Scaffold(body: Center(child: Text('Error: $e'))), + onGenerateTitle: (ctx) => AppLocalizations.of(ctx).appTitle, + supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: + AppLocalizations.localizationsDelegates, + home: Builder( + builder: + (ctx) => Scaffold( + body: Center( + child: Text( + AppLocalizations.of( + ctx, + ).errorWithMessage(e.toString()), + ), + ), + ), + ), ), data: (_) { final themeMode = ref.watch(themeModeProvider); final appLocale = ref.watch(localeProvider); return MaterialApp( - title: 'PDF Signature', + onGenerateTitle: (ctx) => AppLocalizations.of(ctx).appTitle, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.indigo, @@ -39,12 +54,8 @@ class MyApp extends StatelessWidget { ), themeMode: themeMode, locale: appLocale, - supportedLocales: supportedLocales, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], + supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: AppLocalizations.localizationsDelegates, home: const PdfSignatureHomePage(), ); }, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..5881133 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,61 @@ +{ + "@@locale": "en", + "appTitle": "PDF Signature", + "errorWithMessage": "Error: {message}", + "@errorWithMessage": { + "description": "Generic error text with message", + "placeholders": {"message": {"type": "String"}} + }, + + "settings": "Settings", + "theme": "Theme", + "themeLight": "Light", + "themeDark": "Dark", + "themeSystem": "System", + "language": "Language", + "languageEnglish": "English", + "languageChineseTraditional": "Traditional Chinese", + "languageSpanish": "Spanish", + "resetToDefaults": "Reset to defaults", + + "openPdf": "Open PDF...", + "prev": "Prev", + "next": "Next", + "pageInfo": "Page {current}/{total}", + "@pageInfo": { + "description": "Label showing current page and total", + "placeholders": { + "current": {"type": "int"}, + "total": {"type": "int"} + } + }, + "goTo": "Go to:", + "dpi": "DPI:", + "markForSigning": "Mark for Signing", + "unmarkSigning": "Unmark Signing", + "saveSignedPdf": "Save Signed PDF", + "loadSignatureFromFile": "Load Signature from file", + "drawSignature": "Draw Signature", + "noPdfLoaded": "No PDF loaded", + "signature": "Signature", + "lockAspectRatio": "Lock aspect ratio", + "backgroundRemoval": "Background removal", + "contrast": "Contrast", + "brightness": "Brightness", + "exportingPleaseWait": "Exporting... Please wait", + + "nothingToSaveYet": "Nothing to save yet", + "savedWithPath": "Saved: {path}", + "@savedWithPath": { + "description": "Snackbar text showing where file saved", + "placeholders": {"path": {"type": "String"}} + }, + "failedToSavePdf": "Failed to save PDF", + "downloadStarted": "Download started", + "failedToGeneratePdf": "Failed to generate PDF", + "invalidOrUnsupportedFile": "Invalid or unsupported file", + + "confirm": "Confirm", + "undo": "Undo", + "clear": "Clear" +} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb new file mode 100644 index 0000000..1734ed0 --- /dev/null +++ b/lib/l10n/app_es.arb @@ -0,0 +1,46 @@ +{ + "@@locale": "es", + "appTitle": "Firma PDF", + "errorWithMessage": "Error: {message}", + + "settings": "Ajustes", + "theme": "Tema", + "themeLight": "Claro", + "themeDark": "Oscuro", + "themeSystem": "Del sistema", + "language": "Idioma", + "languageEnglish": "Inglés", + "languageChineseTraditional": "Chino tradicional", + "languageSpanish": "Español", + "resetToDefaults": "Restablecer valores", + + "openPdf": "Abrir PDF…", + "prev": "Anterior", + "next": "Siguiente", + "pageInfo": "Página {current}/{total}", + "goTo": "Ir a:", + "dpi": "DPI:", + "markForSigning": "Marcar para firmar", + "unmarkSigning": "Quitar marca", + "saveSignedPdf": "Guardar PDF firmado", + "loadSignatureFromFile": "Cargar firma desde archivo", + "drawSignature": "Dibujar firma", + "noPdfLoaded": "No hay PDF cargado", + "signature": "Firma", + "lockAspectRatio": "Bloquear relación de aspecto", + "backgroundRemoval": "Eliminación de fondo", + "contrast": "Contraste", + "brightness": "Brillo", + "exportingPleaseWait": "Exportando... Por favor espera", + + "nothingToSaveYet": "Nada que guardar todavía", + "savedWithPath": "Guardado: {path}", + "failedToSavePdf": "Error al guardar el PDF", + "downloadStarted": "Descarga iniciada", + "failedToGeneratePdf": "Error al generar el PDF", + "invalidOrUnsupportedFile": "Archivo no válido o no compatible", + + "confirm": "Confirmar", + "undo": "Deshacer", + "clear": "Limpiar" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..37e426a --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,385 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_es.dart'; +import 'app_localizations_zh.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('es'), + Locale('zh'), + Locale('zh', 'TW'), + ]; + + /// No description provided for @appTitle. + /// + /// In en, this message translates to: + /// **'PDF Signature'** + String get appTitle; + + /// Generic error text with message + /// + /// In en, this message translates to: + /// **'Error: {message}'** + String errorWithMessage(String message); + + /// No description provided for @settings. + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// No description provided for @theme. + /// + /// In en, this message translates to: + /// **'Theme'** + String get theme; + + /// No description provided for @themeLight. + /// + /// In en, this message translates to: + /// **'Light'** + String get themeLight; + + /// No description provided for @themeDark. + /// + /// In en, this message translates to: + /// **'Dark'** + String get themeDark; + + /// No description provided for @themeSystem. + /// + /// In en, this message translates to: + /// **'System'** + String get themeSystem; + + /// No description provided for @language. + /// + /// In en, this message translates to: + /// **'Language'** + String get language; + + /// No description provided for @languageEnglish. + /// + /// In en, this message translates to: + /// **'English'** + String get languageEnglish; + + /// No description provided for @languageChineseTraditional. + /// + /// In en, this message translates to: + /// **'Traditional Chinese'** + String get languageChineseTraditional; + + /// No description provided for @languageSpanish. + /// + /// In en, this message translates to: + /// **'Spanish'** + String get languageSpanish; + + /// No description provided for @resetToDefaults. + /// + /// In en, this message translates to: + /// **'Reset to defaults'** + String get resetToDefaults; + + /// No description provided for @openPdf. + /// + /// In en, this message translates to: + /// **'Open PDF...'** + String get openPdf; + + /// No description provided for @prev. + /// + /// In en, this message translates to: + /// **'Prev'** + String get prev; + + /// No description provided for @next. + /// + /// In en, this message translates to: + /// **'Next'** + String get next; + + /// Label showing current page and total + /// + /// In en, this message translates to: + /// **'Page {current}/{total}'** + String pageInfo(int current, int total); + + /// No description provided for @goTo. + /// + /// In en, this message translates to: + /// **'Go to:'** + String get goTo; + + /// No description provided for @dpi. + /// + /// In en, this message translates to: + /// **'DPI:'** + String get dpi; + + /// No description provided for @markForSigning. + /// + /// In en, this message translates to: + /// **'Mark for Signing'** + String get markForSigning; + + /// No description provided for @unmarkSigning. + /// + /// In en, this message translates to: + /// **'Unmark Signing'** + String get unmarkSigning; + + /// No description provided for @saveSignedPdf. + /// + /// In en, this message translates to: + /// **'Save Signed PDF'** + String get saveSignedPdf; + + /// No description provided for @loadSignatureFromFile. + /// + /// In en, this message translates to: + /// **'Load Signature from file'** + String get loadSignatureFromFile; + + /// No description provided for @drawSignature. + /// + /// In en, this message translates to: + /// **'Draw Signature'** + String get drawSignature; + + /// No description provided for @noPdfLoaded. + /// + /// In en, this message translates to: + /// **'No PDF loaded'** + String get noPdfLoaded; + + /// No description provided for @signature. + /// + /// In en, this message translates to: + /// **'Signature'** + String get signature; + + /// No description provided for @lockAspectRatio. + /// + /// In en, this message translates to: + /// **'Lock aspect ratio'** + String get lockAspectRatio; + + /// No description provided for @backgroundRemoval. + /// + /// In en, this message translates to: + /// **'Background removal'** + String get backgroundRemoval; + + /// No description provided for @contrast. + /// + /// In en, this message translates to: + /// **'Contrast'** + String get contrast; + + /// No description provided for @brightness. + /// + /// In en, this message translates to: + /// **'Brightness'** + String get brightness; + + /// No description provided for @exportingPleaseWait. + /// + /// In en, this message translates to: + /// **'Exporting... Please wait'** + String get exportingPleaseWait; + + /// No description provided for @nothingToSaveYet. + /// + /// In en, this message translates to: + /// **'Nothing to save yet'** + String get nothingToSaveYet; + + /// Snackbar text showing where file saved + /// + /// In en, this message translates to: + /// **'Saved: {path}'** + String savedWithPath(String path); + + /// No description provided for @failedToSavePdf. + /// + /// In en, this message translates to: + /// **'Failed to save PDF'** + String get failedToSavePdf; + + /// No description provided for @downloadStarted. + /// + /// In en, this message translates to: + /// **'Download started'** + String get downloadStarted; + + /// No description provided for @failedToGeneratePdf. + /// + /// In en, this message translates to: + /// **'Failed to generate PDF'** + String get failedToGeneratePdf; + + /// No description provided for @invalidOrUnsupportedFile. + /// + /// In en, this message translates to: + /// **'Invalid or unsupported file'** + String get invalidOrUnsupportedFile; + + /// No description provided for @confirm. + /// + /// In en, this message translates to: + /// **'Confirm'** + String get confirm; + + /// No description provided for @undo. + /// + /// In en, this message translates to: + /// **'Undo'** + String get undo; + + /// No description provided for @clear. + /// + /// In en, this message translates to: + /// **'Clear'** + String get clear; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'es', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': + { + switch (locale.countryCode) { + case 'TW': + return AppLocalizationsZhTw(); + } + break; + } + } + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'es': + return AppLocalizationsEs(); + case 'zh': + return AppLocalizationsZh(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..2c43b77 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,133 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appTitle => 'PDF Signature'; + + @override + String errorWithMessage(String message) { + return 'Error: $message'; + } + + @override + String get settings => 'Settings'; + + @override + String get theme => 'Theme'; + + @override + String get themeLight => 'Light'; + + @override + String get themeDark => 'Dark'; + + @override + String get themeSystem => 'System'; + + @override + String get language => 'Language'; + + @override + String get languageEnglish => 'English'; + + @override + String get languageChineseTraditional => 'Traditional Chinese'; + + @override + String get languageSpanish => 'Spanish'; + + @override + String get resetToDefaults => 'Reset to defaults'; + + @override + String get openPdf => 'Open PDF...'; + + @override + String get prev => 'Prev'; + + @override + String get next => 'Next'; + + @override + String pageInfo(int current, int total) { + return 'Page $current/$total'; + } + + @override + String get goTo => 'Go to:'; + + @override + String get dpi => 'DPI:'; + + @override + String get markForSigning => 'Mark for Signing'; + + @override + String get unmarkSigning => 'Unmark Signing'; + + @override + String get saveSignedPdf => 'Save Signed PDF'; + + @override + String get loadSignatureFromFile => 'Load Signature from file'; + + @override + String get drawSignature => 'Draw Signature'; + + @override + String get noPdfLoaded => 'No PDF loaded'; + + @override + String get signature => 'Signature'; + + @override + String get lockAspectRatio => 'Lock aspect ratio'; + + @override + String get backgroundRemoval => 'Background removal'; + + @override + String get contrast => 'Contrast'; + + @override + String get brightness => 'Brightness'; + + @override + String get exportingPleaseWait => 'Exporting... Please wait'; + + @override + String get nothingToSaveYet => 'Nothing to save yet'; + + @override + String savedWithPath(String path) { + return 'Saved: $path'; + } + + @override + String get failedToSavePdf => 'Failed to save PDF'; + + @override + String get downloadStarted => 'Download started'; + + @override + String get failedToGeneratePdf => 'Failed to generate PDF'; + + @override + String get invalidOrUnsupportedFile => 'Invalid or unsupported file'; + + @override + String get confirm => 'Confirm'; + + @override + String get undo => 'Undo'; + + @override + String get clear => 'Clear'; +} diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart new file mode 100644 index 0000000..5ea117b --- /dev/null +++ b/lib/l10n/app_localizations_es.dart @@ -0,0 +1,133 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Spanish Castilian (`es`). +class AppLocalizationsEs extends AppLocalizations { + AppLocalizationsEs([String locale = 'es']) : super(locale); + + @override + String get appTitle => 'Firma PDF'; + + @override + String errorWithMessage(String message) { + return 'Error: $message'; + } + + @override + String get settings => 'Ajustes'; + + @override + String get theme => 'Tema'; + + @override + String get themeLight => 'Claro'; + + @override + String get themeDark => 'Oscuro'; + + @override + String get themeSystem => 'Del sistema'; + + @override + String get language => 'Idioma'; + + @override + String get languageEnglish => 'Inglés'; + + @override + String get languageChineseTraditional => 'Chino tradicional'; + + @override + String get languageSpanish => 'Español'; + + @override + String get resetToDefaults => 'Restablecer valores'; + + @override + String get openPdf => 'Abrir PDF…'; + + @override + String get prev => 'Anterior'; + + @override + String get next => 'Siguiente'; + + @override + String pageInfo(int current, int total) { + return 'Página $current/$total'; + } + + @override + String get goTo => 'Ir a:'; + + @override + String get dpi => 'DPI:'; + + @override + String get markForSigning => 'Marcar para firmar'; + + @override + String get unmarkSigning => 'Quitar marca'; + + @override + String get saveSignedPdf => 'Guardar PDF firmado'; + + @override + String get loadSignatureFromFile => 'Cargar firma desde archivo'; + + @override + String get drawSignature => 'Dibujar firma'; + + @override + String get noPdfLoaded => 'No hay PDF cargado'; + + @override + String get signature => 'Firma'; + + @override + String get lockAspectRatio => 'Bloquear relación de aspecto'; + + @override + String get backgroundRemoval => 'Eliminación de fondo'; + + @override + String get contrast => 'Contraste'; + + @override + String get brightness => 'Brillo'; + + @override + String get exportingPleaseWait => 'Exportando... Por favor espera'; + + @override + String get nothingToSaveYet => 'Nada que guardar todavía'; + + @override + String savedWithPath(String path) { + return 'Guardado: $path'; + } + + @override + String get failedToSavePdf => 'Error al guardar el PDF'; + + @override + String get downloadStarted => 'Descarga iniciada'; + + @override + String get failedToGeneratePdf => 'Error al generar el PDF'; + + @override + String get invalidOrUnsupportedFile => 'Archivo no válido o no compatible'; + + @override + String get confirm => 'Confirmar'; + + @override + String get undo => 'Deshacer'; + + @override + String get clear => 'Limpiar'; +} diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart new file mode 100644 index 0000000..bf8e60f --- /dev/null +++ b/lib/l10n/app_localizations_zh.dart @@ -0,0 +1,261 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Chinese (`zh`). +class AppLocalizationsZh extends AppLocalizations { + AppLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get appTitle => 'PDF 簽名'; + + @override + String errorWithMessage(String message) { + return '錯誤:$message'; + } + + @override + String get settings => '設定'; + + @override + String get theme => '主題'; + + @override + String get themeLight => '淺色'; + + @override + String get themeDark => '深色'; + + @override + String get themeSystem => '系統'; + + @override + String get language => '語言'; + + @override + String get languageEnglish => '英文'; + + @override + String get languageChineseTraditional => '繁體中文'; + + @override + String get languageSpanish => '西班牙文'; + + @override + String get resetToDefaults => '重設為預設值'; + + @override + String get openPdf => '開啟 PDF…'; + + @override + String get prev => '上一頁'; + + @override + String get next => '下一頁'; + + @override + String pageInfo(int current, int total) { + return '第 $current/$total 頁'; + } + + @override + String get goTo => '前往:'; + + @override + String get dpi => 'DPI:'; + + @override + String get markForSigning => '標記簽署'; + + @override + String get unmarkSigning => '取消標記'; + + @override + String get saveSignedPdf => '儲存已簽名 PDF'; + + @override + String get loadSignatureFromFile => '從檔案載入簽名'; + + @override + String get drawSignature => '手寫簽名'; + + @override + String get noPdfLoaded => '尚未載入 PDF'; + + @override + String get signature => '簽名'; + + @override + String get lockAspectRatio => '鎖定長寬比'; + + @override + String get backgroundRemoval => '去除背景'; + + @override + String get contrast => '對比'; + + @override + String get brightness => '亮度'; + + @override + String get exportingPleaseWait => '匯出中…請稍候'; + + @override + String get nothingToSaveYet => '尚無可儲存的內容'; + + @override + String savedWithPath(String path) { + return '已儲存:$path'; + } + + @override + String get failedToSavePdf => '儲存 PDF 失敗'; + + @override + String get downloadStarted => '已開始下載'; + + @override + String get failedToGeneratePdf => '產生 PDF 失敗'; + + @override + String get invalidOrUnsupportedFile => '無效或不支援的檔案'; + + @override + String get confirm => '確認'; + + @override + String get undo => '復原'; + + @override + String get clear => '清除'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class AppLocalizationsZhTw extends AppLocalizationsZh { + AppLocalizationsZhTw() : super('zh_TW'); + + @override + String get appTitle => 'PDF 簽名'; + + @override + String errorWithMessage(String message) { + return '錯誤:$message'; + } + + @override + String get settings => '設定'; + + @override + String get theme => '主題'; + + @override + String get themeLight => '淺色'; + + @override + String get themeDark => '深色'; + + @override + String get themeSystem => '系統'; + + @override + String get language => '語言'; + + @override + String get languageEnglish => '英文'; + + @override + String get languageChineseTraditional => '繁體中文'; + + @override + String get languageSpanish => '西班牙文'; + + @override + String get resetToDefaults => '重設為預設值'; + + @override + String get openPdf => '開啟 PDF…'; + + @override + String get prev => '上一頁'; + + @override + String get next => '下一頁'; + + @override + String pageInfo(int current, int total) { + return '第 $current/$total 頁'; + } + + @override + String get goTo => '前往:'; + + @override + String get dpi => 'DPI:'; + + @override + String get markForSigning => '標記簽署'; + + @override + String get unmarkSigning => '取消標記'; + + @override + String get saveSignedPdf => '儲存已簽名 PDF'; + + @override + String get loadSignatureFromFile => '從檔案載入簽名'; + + @override + String get drawSignature => '手寫簽名'; + + @override + String get noPdfLoaded => '尚未載入 PDF'; + + @override + String get signature => '簽名'; + + @override + String get lockAspectRatio => '鎖定長寬比'; + + @override + String get backgroundRemoval => '去除背景'; + + @override + String get contrast => '對比'; + + @override + String get brightness => '亮度'; + + @override + String get exportingPleaseWait => '匯出中…請稍候'; + + @override + String get nothingToSaveYet => '尚無可儲存的內容'; + + @override + String savedWithPath(String path) { + return '已儲存:$path'; + } + + @override + String get failedToSavePdf => '儲存 PDF 失敗'; + + @override + String get downloadStarted => '已開始下載'; + + @override + String get failedToGeneratePdf => '產生 PDF 失敗'; + + @override + String get invalidOrUnsupportedFile => '無效或不支援的檔案'; + + @override + String get confirm => '確認'; + + @override + String get undo => '復原'; + + @override + String get clear => '清除'; +} diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb new file mode 100644 index 0000000..b7b9573 --- /dev/null +++ b/lib/l10n/app_zh.arb @@ -0,0 +1,46 @@ +{ + "@@locale": "zh", + "appTitle": "PDF 簽名", + "errorWithMessage": "錯誤:{message}", + + "settings": "設定", + "theme": "主題", + "themeLight": "淺色", + "themeDark": "深色", + "themeSystem": "系統", + "language": "語言", + "languageEnglish": "英文", + "languageChineseTraditional": "繁體中文", + "languageSpanish": "西班牙文", + "resetToDefaults": "重設為預設值", + + "openPdf": "開啟 PDF…", + "prev": "上一頁", + "next": "下一頁", + "pageInfo": "第 {current}/{total} 頁", + "goTo": "前往:", + "dpi": "DPI:", + "markForSigning": "標記簽署", + "unmarkSigning": "取消標記", + "saveSignedPdf": "儲存已簽名 PDF", + "loadSignatureFromFile": "從檔案載入簽名", + "drawSignature": "手寫簽名", + "noPdfLoaded": "尚未載入 PDF", + "signature": "簽名", + "lockAspectRatio": "鎖定長寬比", + "backgroundRemoval": "去除背景", + "contrast": "對比", + "brightness": "亮度", + "exportingPleaseWait": "匯出中…請稍候", + + "nothingToSaveYet": "尚無可儲存的內容", + "savedWithPath": "已儲存:{path}", + "failedToSavePdf": "儲存 PDF 失敗", + "downloadStarted": "已開始下載", + "failedToGeneratePdf": "產生 PDF 失敗", + "invalidOrUnsupportedFile": "無效或不支援的檔案", + + "confirm": "確認", + "undo": "復原", + "clear": "清除" +} diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb new file mode 100644 index 0000000..7fbb248 --- /dev/null +++ b/lib/l10n/app_zh_TW.arb @@ -0,0 +1,46 @@ +{ + "@@locale": "zh_TW", + "appTitle": "PDF 簽名", + "errorWithMessage": "錯誤:{message}", + + "settings": "設定", + "theme": "主題", + "themeLight": "淺色", + "themeDark": "深色", + "themeSystem": "系統", + "language": "語言", + "languageEnglish": "英文", + "languageChineseTraditional": "繁體中文", + "languageSpanish": "西班牙文", + "resetToDefaults": "重設為預設值", + + "openPdf": "開啟 PDF…", + "prev": "上一頁", + "next": "下一頁", + "pageInfo": "第 {current}/{total} 頁", + "goTo": "前往:", + "dpi": "DPI:", + "markForSigning": "標記簽署", + "unmarkSigning": "取消標記", + "saveSignedPdf": "儲存已簽名 PDF", + "loadSignatureFromFile": "從檔案載入簽名", + "drawSignature": "手寫簽名", + "noPdfLoaded": "尚未載入 PDF", + "signature": "簽名", + "lockAspectRatio": "鎖定長寬比", + "backgroundRemoval": "去除背景", + "contrast": "對比", + "brightness": "亮度", + "exportingPleaseWait": "匯出中…請稍候", + + "nothingToSaveYet": "尚無可儲存的內容", + "savedWithPath": "已儲存:{path}", + "failedToSavePdf": "儲存 PDF 失敗", + "downloadStarted": "已開始下載", + "failedToGeneratePdf": "產生 PDF 失敗", + "invalidOrUnsupportedFile": "無效或不支援的檔案", + + "confirm": "確認", + "undo": "復原", + "clear": "清除" +} diff --git a/lib/ui/features/pdf/view_model/view_model.dart b/lib/ui/features/pdf/view_model/view_model.dart index 2be99d2..d2d6baf 100644 --- a/lib/ui/features/pdf/view_model/view_model.dart +++ b/lib/ui/features/pdf/view_model/view_model.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:image/image.dart' as img; import '../../../../data/model/model.dart'; @@ -96,9 +97,10 @@ class SignatureController extends StateNotifier { } void setInvalidSelected(BuildContext context) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Invalid or unsupported file')), - ); + final l = AppLocalizations.of(context); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(l.invalidOrUnsupportedFile))); } void drag(Offset delta) { diff --git a/lib/ui/features/pdf/widgets/draw_canvas.dart b/lib/ui/features/pdf/widgets/draw_canvas.dart index 2b7344b..17d17d5 100644 --- a/lib/ui/features/pdf/widgets/draw_canvas.dart +++ b/lib/ui/features/pdf/widgets/draw_canvas.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:hand_signature/signature.dart' as hand; class DrawCanvas extends StatefulWidget { @@ -35,6 +36,7 @@ class _DrawCanvasState extends State { @override Widget build(BuildContext context) { + final l = AppLocalizations.of(context); return SafeArea( child: Padding( padding: const EdgeInsets.all(12), @@ -64,19 +66,19 @@ class _DrawCanvasState extends State { } } }, - child: const Text('Confirm'), + child: Text(l.confirm), ), const SizedBox(width: 8), OutlinedButton( key: const Key('btn_canvas_undo'), onPressed: () => _control.stepBack(), - child: const Text('Undo'), + child: Text(l.undo), ), const SizedBox(width: 8), OutlinedButton( key: const Key('btn_canvas_clear'), onPressed: () => _control.clear(), - child: const Text('Clear'), + child: Text(l.clear), ), ], ), diff --git a/lib/ui/features/pdf/widgets/pdf_screen.dart b/lib/ui/features/pdf/widgets/pdf_screen.dart index 362d67d..f40d14a 100644 --- a/lib/ui/features/pdf/widgets/pdf_screen.dart +++ b/lib/ui/features/pdf/widgets/pdf_screen.dart @@ -4,6 +4,7 @@ import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdfrx/pdfrx.dart'; import 'package:printing/printing.dart' as printing; @@ -101,8 +102,8 @@ class _PdfSignatureHomePageState extends ConsumerState { final messenger = ScaffoldMessenger.of(context); if (!pdf.loaded || sig.rect == null) { messenger.showSnackBar( - const SnackBar( - content: Text('Nothing to save yet'), + SnackBar( + content: Text(AppLocalizations.of(context).nothingToSaveYet), ), // guard per use-case ); return; @@ -193,22 +194,32 @@ class _PdfSignatureHomePageState extends ConsumerState { // Desktop/mobile: we had a concrete path if (ok) { messenger.showSnackBar( - SnackBar(content: Text('Saved: ${savedPath ?? ''}')), + SnackBar( + content: Text( + AppLocalizations.of(context).savedWithPath(savedPath ?? ''), + ), + ), ); } else { messenger.showSnackBar( - const SnackBar(content: Text('Failed to save PDF')), + SnackBar( + content: Text(AppLocalizations.of(context).failedToSavePdf), + ), ); } } else { // Web: indicate whether we triggered a download dialog if (ok) { messenger.showSnackBar( - const SnackBar(content: Text('Download started')), + SnackBar( + content: Text(AppLocalizations.of(context).downloadStarted), + ), ); } else { messenger.showSnackBar( - const SnackBar(content: Text('Failed to generate PDF')), + SnackBar( + content: Text(AppLocalizations.of(context).failedToGeneratePdf), + ), ); } } @@ -227,8 +238,9 @@ class _PdfSignatureHomePageState extends ConsumerState { Widget build(BuildContext context) { final pdf = ref.watch(pdfProvider); final isExporting = ref.watch(exportingProvider); + final l = AppLocalizations.of(context); return Scaffold( - appBar: AppBar(title: const Text('PDF Signature')), + appBar: AppBar(title: Text(l.appTitle)), body: Padding( padding: const EdgeInsets.all(12), child: Stack( @@ -260,15 +272,15 @@ class _PdfSignatureHomePageState extends ConsumerState { Positioned.fill( child: Container( color: Colors.black45, - child: const Center( + child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 12), Text( - 'Exporting... Please wait', - style: TextStyle(color: Colors.white), + l.exportingPleaseWait, + style: const TextStyle(color: Colors.white), ), ], ), @@ -283,7 +295,8 @@ class _PdfSignatureHomePageState extends ConsumerState { Widget _buildToolbar(PdfState pdf, {bool disabled = false}) { final dpi = ref.watch(exportDpiProvider); - final pageInfo = 'Page ${pdf.currentPage}/${pdf.pageCount}'; + final l = AppLocalizations.of(context); + final pageInfo = l.pageInfo(pdf.currentPage, pdf.pageCount); return Wrap( spacing: 8, runSpacing: 8, @@ -299,12 +312,12 @@ class _PdfSignatureHomePageState extends ConsumerState { MaterialPageRoute(builder: (_) => const SettingsScreen()), ); }, - child: const Text('Settings'), + child: Text(l.settings), ), OutlinedButton( key: const Key('btn_open_pdf_picker'), onPressed: disabled ? null : _pickPdf, - child: const Text('Open PDF...'), + child: Text(l.openPdf), ), if (pdf.loaded) ...[ Row( @@ -315,7 +328,7 @@ class _PdfSignatureHomePageState extends ConsumerState { onPressed: disabled ? null : () => _jumpToPage(pdf.currentPage - 1), icon: const Icon(Icons.chevron_left), - tooltip: 'Prev', + tooltip: l.prev, ), Text(pageInfo, key: const Key('lbl_page_info')), IconButton( @@ -323,14 +336,14 @@ class _PdfSignatureHomePageState extends ConsumerState { onPressed: disabled ? null : () => _jumpToPage(pdf.currentPage + 1), icon: const Icon(Icons.chevron_right), - tooltip: 'Next', + tooltip: l.next, ), ], ), Row( mainAxisSize: MainAxisSize.min, children: [ - const Text('Go to:'), + Text(l.goTo), SizedBox( width: 60, child: TextField( @@ -348,7 +361,7 @@ class _PdfSignatureHomePageState extends ConsumerState { Row( mainAxisSize: MainAxisSize.min, children: [ - const Text('DPI:'), + Text(l.dpi), const SizedBox(width: 8), DropdownButton( key: const Key('ddl_export_dpi'), @@ -377,25 +390,25 @@ class _PdfSignatureHomePageState extends ConsumerState { key: const Key('btn_mark_signing'), onPressed: disabled ? null : _toggleMarkForSigning, child: Text( - pdf.markedForSigning ? 'Unmark Signing' : 'Mark for Signing', + pdf.markedForSigning ? l.unmarkSigning : l.markForSigning, ), ), if (pdf.loaded) ElevatedButton( key: const Key('btn_save_pdf'), onPressed: disabled ? null : _saveSignedPdf, - child: const Text('Save Signed PDF'), + child: Text(l.saveSignedPdf), ), if (pdf.markedForSigning) ...[ OutlinedButton( key: const Key('btn_load_signature_picker'), onPressed: disabled ? null : _loadSignatureFromFile, - child: const Text('Load Signature from file'), + child: Text(l.loadSignatureFromFile), ), ElevatedButton( key: const Key('btn_draw_signature'), onPressed: disabled ? null : _openDrawCanvas, - child: const Text('Draw Signature'), + child: Text(l.drawSignature), ), ], ], @@ -405,7 +418,7 @@ class _PdfSignatureHomePageState extends ConsumerState { Widget _buildPageArea(PdfState pdf) { if (!pdf.loaded) { - return const Center(child: Text('No PDF loaded')); + return Center(child: Text(AppLocalizations.of(context).noPdfLoaded)); } final useMock = ref.watch(useMockViewerProvider); if (useMock) { @@ -422,7 +435,9 @@ class _PdfSignatureHomePageState extends ConsumerState { color: Colors.grey.shade200, child: Center( child: Text( - 'Page ${pdf.currentPage}/${pdf.pageCount}', + AppLocalizations.of( + context, + ).pageInfo(pdf.currentPage, pdf.pageCount), style: const TextStyle( fontSize: 24, color: Colors.black54, @@ -544,7 +559,11 @@ class _PdfSignatureHomePageState extends ConsumerState { ); final bytes = processed ?? sig.imageBytes; if (bytes == null) { - return const Center(child: Text('Signature')); + return Center( + child: Text( + AppLocalizations.of(context).signature, + ), + ); } return Image.memory(bytes, fit: BoxFit.contain); }, @@ -590,7 +609,7 @@ class _PdfSignatureHomePageState extends ConsumerState { .read(signatureProvider.notifier) .toggleAspect(v ?? false), ), - const Text('Lock aspect ratio'), + Text(AppLocalizations.of(context).lockAspectRatio), const SizedBox(width: 16), Switch( key: const Key('swt_bg_removal'), @@ -598,12 +617,12 @@ class _PdfSignatureHomePageState extends ConsumerState { onChanged: (v) => ref.read(signatureProvider.notifier).setBgRemoval(v), ), - const Text('Background removal'), + Text(AppLocalizations.of(context).backgroundRemoval), ], ), Row( children: [ - const Text('Contrast'), + Text(AppLocalizations.of(context).contrast), Expanded( child: Slider( key: const Key('sld_contrast'), @@ -619,7 +638,7 @@ class _PdfSignatureHomePageState extends ConsumerState { ), Row( children: [ - const Text('Brightness'), + Text(AppLocalizations.of(context).brightness), Expanded( child: Slider( key: const Key('sld_brightness'), diff --git a/lib/ui/features/preferences/widgets/settings_screen.dart b/lib/ui/features/preferences/widgets/settings_screen.dart index 7062f40..bac6f79 100644 --- a/lib/ui/features/preferences/widgets/settings_screen.dart +++ b/lib/ui/features/preferences/widgets/settings_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; import '../providers.dart'; class SettingsScreen extends ConsumerWidget { @@ -8,22 +9,23 @@ class SettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final prefs = ref.watch(preferencesProvider); + final l = AppLocalizations.of(context); return Scaffold( - appBar: AppBar(title: const Text('Settings')), + appBar: AppBar(title: Text(l.settings)), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Theme', style: TextStyle(fontWeight: FontWeight.bold)), + Text(l.theme, style: const TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), DropdownButton( key: const Key('ddl_theme'), value: prefs.theme, - items: const [ - DropdownMenuItem(value: 'light', child: Text('Light')), - DropdownMenuItem(value: 'dark', child: Text('Dark')), - DropdownMenuItem(value: 'system', child: Text('System')), + items: [ + DropdownMenuItem(value: 'light', child: Text(l.themeLight)), + DropdownMenuItem(value: 'dark', child: Text(l.themeDark)), + DropdownMenuItem(value: 'system', child: Text(l.themeSystem)), ], onChanged: (v) => @@ -32,18 +34,21 @@ class SettingsScreen extends ConsumerWidget { : ref.read(preferencesProvider.notifier).setTheme(v), ), const SizedBox(height: 16), - const Text( - 'Language', - style: TextStyle(fontWeight: FontWeight.bold), + Text( + l.language, + style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), DropdownButton( key: const Key('ddl_language'), value: prefs.language, - items: const [ - DropdownMenuItem(value: 'en', child: Text('English')), - DropdownMenuItem(value: 'zh-TW', child: Text('繁體中文')), - DropdownMenuItem(value: 'es', child: Text('Español')), + items: [ + DropdownMenuItem(value: 'en', child: Text(l.languageEnglish)), + DropdownMenuItem( + value: 'zh-TW', + child: Text(l.languageChineseTraditional), + ), + DropdownMenuItem(value: 'es', child: Text(l.languageSpanish)), ], onChanged: (v) => @@ -63,7 +68,7 @@ class SettingsScreen extends ConsumerWidget { ref .read(preferencesProvider.notifier) .resetToDefaults(), - child: const Text('Reset to defaults'), + child: Text(l.resetToDefaults), ), ), ], diff --git a/pubspec.yaml b/pubspec.yaml index 7235a90..64c9bee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,7 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: + generate: true # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in