diff --git a/assets/i18n/en.json b/assets/i18n/en.json new file mode 100644 index 0000000..2a6aaa8 --- /dev/null +++ b/assets/i18n/en.json @@ -0,0 +1,73 @@ +{ + "appTitle": "Wave", + "@appTitle": { + "description": "The title of the application" + }, + "start_screen": { + "welcome_text": "Welcome!", + "start_button": "Start" + }, + "start_connection_screen": { + "create_code_text": "Create a code", + "initiate_text": "If you want to initiate a connection", + "create_button": "Create", + "or_text": "OR", + "paste_code_text": "Paste code from friend", + "connect_text": "If you want to connect to already created peer", + "paste_button": "Paste" + }, + "enable_microphone_screen": { + "allow_access_text": "Allow access, please", + "mic_on_button": "Mic on" + }, + "copy_code_screen": { + "your_code_text": "This is your two-word pair cod. Copy and send it to your friend", + "check_button": "Check pair", + "wait_text": "Wait your friend to paste the code for button enabling", + "fail_create_code_text": "Failed to create code" + }, + + "paste_code_screen": { + "paste_code_text": "Copy your friend’s code and paste it to the text input below:", + "connect_button": "Connect" + }, + + "connection_screen": { + "close_peer_button": "Close peer", + "warn_termination_text": "This leads to the termination of your connection", + "help_text": "This might help: ", + "return_to_prev_step_text": "Return to the previous step and try to pair once again", + "return_button": "Return", + "connected_text": "Connected", + "connecting_text": "Connecting", + "fail_to_connect_text": "Failed to connect", + "disconnected_text": "Disconnected", + "successful_connection_text": "Successful connection!", + "device_to_connect_text": "Waiting other device to connect..", + "device_to_accept_text": "Waiting your friend’s device to accept...", + "device_to_answer_text": "Waiting your friend’s device to answer...", + "failed_text": "Failed!", + "connection_lost_text": "Connection lost!" + }, + + "main_screen": { + "invalid_text": "Invalid two-word code" + }, + + "call_screen": { + "you_text": "You", + "peer_text": "Peer", + "encrypted_text": "Your call is end-to-end encrypted", + "current_input_device_text": "Current Input Device", + "current_output_device_text": "Current Output Device", + "default_microphone_text": "Default Microphone", + "default_speaker_text": "Default Speaker", + "settings_text": "Settings", + "leave_call_text": "Leave Call", + "join_call_text": "Join Call", + "connected_text": "Connected", + "call_failed_text": "Call Failed", + "connecting_text": "Connecting...", + "ready_text": "Ready to call" + } +} \ No newline at end of file diff --git a/assets/i18n/ru.json b/assets/i18n/ru.json new file mode 100644 index 0000000..2a6aaa8 --- /dev/null +++ b/assets/i18n/ru.json @@ -0,0 +1,73 @@ +{ + "appTitle": "Wave", + "@appTitle": { + "description": "The title of the application" + }, + "start_screen": { + "welcome_text": "Welcome!", + "start_button": "Start" + }, + "start_connection_screen": { + "create_code_text": "Create a code", + "initiate_text": "If you want to initiate a connection", + "create_button": "Create", + "or_text": "OR", + "paste_code_text": "Paste code from friend", + "connect_text": "If you want to connect to already created peer", + "paste_button": "Paste" + }, + "enable_microphone_screen": { + "allow_access_text": "Allow access, please", + "mic_on_button": "Mic on" + }, + "copy_code_screen": { + "your_code_text": "This is your two-word pair cod. Copy and send it to your friend", + "check_button": "Check pair", + "wait_text": "Wait your friend to paste the code for button enabling", + "fail_create_code_text": "Failed to create code" + }, + + "paste_code_screen": { + "paste_code_text": "Copy your friend’s code and paste it to the text input below:", + "connect_button": "Connect" + }, + + "connection_screen": { + "close_peer_button": "Close peer", + "warn_termination_text": "This leads to the termination of your connection", + "help_text": "This might help: ", + "return_to_prev_step_text": "Return to the previous step and try to pair once again", + "return_button": "Return", + "connected_text": "Connected", + "connecting_text": "Connecting", + "fail_to_connect_text": "Failed to connect", + "disconnected_text": "Disconnected", + "successful_connection_text": "Successful connection!", + "device_to_connect_text": "Waiting other device to connect..", + "device_to_accept_text": "Waiting your friend’s device to accept...", + "device_to_answer_text": "Waiting your friend’s device to answer...", + "failed_text": "Failed!", + "connection_lost_text": "Connection lost!" + }, + + "main_screen": { + "invalid_text": "Invalid two-word code" + }, + + "call_screen": { + "you_text": "You", + "peer_text": "Peer", + "encrypted_text": "Your call is end-to-end encrypted", + "current_input_device_text": "Current Input Device", + "current_output_device_text": "Current Output Device", + "default_microphone_text": "Default Microphone", + "default_speaker_text": "Default Speaker", + "settings_text": "Settings", + "leave_call_text": "Leave Call", + "join_call_text": "Join Call", + "connected_text": "Connected", + "call_failed_text": "Call Failed", + "connecting_text": "Connecting...", + "ready_text": "Ready to call" + } +} \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml deleted file mode 100644 index d480072..0000000 --- a/l10n.yaml +++ /dev/null @@ -1,3 +0,0 @@ -arb-dir: lib/src/localization -template-arb-file: app_en.arb -output-localization-file: app_localizations.dart diff --git a/lib/src/i18n/localizations.dart b/lib/src/i18n/localizations.dart new file mode 100644 index 0000000..3994c89 --- /dev/null +++ b/lib/src/i18n/localizations.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AppLocalisationDelegate extends LocalizationsDelegate { + const AppLocalisationDelegate(); + + @override + bool isSupported(Locale locale) => AppLocalizations.isSupported(locale); + + @override + bool shouldReload(LocalizationsDelegate old) => false; + + @override + Future load(Locale locale) async { + final loc = AppLocalizations(AppLocalizations.fetchLocale(locale)); + await loc.load(); + return loc; + } +} + +class AppLocalizations { + AppLocalizations(this.locale); + final Locale locale; + + late final Map _keys; + + static const Locale defaultLocale = Locale('en'); + static const supportedLocales = [Locale('ru'), defaultLocale]; + static const supportedLanguageCodes = {'en', 'ru'}; + + static bool isSupported(Locale locale) => + supportedLanguageCodes.contains(locale.languageCode); + + static Locale fetchLocale(Locale locale) => + isSupported(locale) ? Locale(locale.languageCode) : defaultLocale; + + Future load() async { + // 1) База en + final base = await _loadJsonMap('assets/i18n/en.json'); + // 2) Текущий язык (может совпадать с en) + final lang = locale.languageCode; + final overlay = lang == 'en' + ? const {} + : await _tryLoadJsonMap('assets/i18n/$lang.json'); + + // merge и сплющивание в плоские ключи "a.b.c" + final merged = {} + ..addAll(base) + ..addAll(overlay); + _keys = _flatten(merged); + } + + // ---- API ---- + + String t(String key, {Map params = const {}}) { + final raw = _keys[key] ?? key; + if (params.isEmpty) return raw; + return _fillPlaceholders(raw, params); + } + + String translate(String key, [Map? placeholders]) => + t(key, params: placeholders ?? const {}); + + static AppLocalizations of(BuildContext context) => + Localizations.of(context, AppLocalizations)!; + + // ---- helpers ---- + + Future> _loadJsonMap(String path) async { + final raw = await rootBundle.loadString(path); + return (json.decode(raw) as Map); + } + + Future> _tryLoadJsonMap(String path) async { + try { + final raw = await rootBundle.loadString(path); + return (json.decode(raw) as Map); + } catch (_) { + return const {}; + } + } + + Map _flatten(Map map, {String? prefix}) { + final out = {}; + + map.forEach((k, v) { + // пропускаем служебные ключи (на будущее, под ARB-стиль) + if (k.startsWith('@')) return; + + final key = prefix == null ? k : '$prefix.$k'; + if (v is Map) { + out.addAll(_flatten(v.cast(), prefix: key)); + } else if (v is String) { + out[key] = v; + } else if (v != null) { + out[key] = v.toString(); + } + }); + + return out; + } + + String _fillPlaceholders(String text, Map params) { + // поддерживаем как ${name}, так и {name} + return text.replaceAllMapped(RegExp(r'\$\{(\w+)\}|\{(\w+)\}'), (m) { + final key = m.group(1) ?? m.group(2)!; + final val = params[key]; + return val?.toString() ?? m.group(0)!; + }); + } +} + +// syntactic sugar: context.l10n.t('key') +extension L10nX on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this); +} diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb deleted file mode 100644 index df04ea5..0000000 --- a/lib/src/localization/app_en.arb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "appTitle": "Wave", - "@appTitle": { - "description": "The title of the application" - } -} diff --git a/lib/src/localization/app_localizations.dart b/lib/src/localization/app_localizations.dart deleted file mode 100644 index c3a351e..0000000 --- a/lib/src/localization/app_localizations.dart +++ /dev/null @@ -1,140 +0,0 @@ -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_ru.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 'localization/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('ru'), - ]; - - /// The title of the application - /// - /// In en, this message translates to: - /// **'Wave'** - String get appTitle; -} - -class _AppLocalizationsDelegate - extends LocalizationsDelegate { - const _AppLocalizationsDelegate(); - - @override - Future load(Locale locale) { - return SynchronousFuture(lookupAppLocalizations(locale)); - } - - @override - bool isSupported(Locale locale) => - ['en', 'ru'].contains(locale.languageCode); - - @override - bool shouldReload(_AppLocalizationsDelegate old) => false; -} - -AppLocalizations lookupAppLocalizations(Locale locale) { - // Lookup logic when only language code is specified. - switch (locale.languageCode) { - case 'en': - return AppLocalizationsEn(); - case 'ru': - return AppLocalizationsRu(); - } - - 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/src/localization/app_localizations_en.dart b/lib/src/localization/app_localizations_en.dart deleted file mode 100644 index 5adbb7a..0000000 --- a/lib/src/localization/app_localizations_en.dart +++ /dev/null @@ -1,13 +0,0 @@ -// 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 => 'Wave'; -} diff --git a/lib/src/localization/app_localizations_ru.dart b/lib/src/localization/app_localizations_ru.dart deleted file mode 100644 index 90e3c1f..0000000 --- a/lib/src/localization/app_localizations_ru.dart +++ /dev/null @@ -1,13 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for Russian (`ru`). -class AppLocalizationsRu extends AppLocalizations { - AppLocalizationsRu([String locale = 'ru']) : super(locale); - - @override - String get appTitle => 'Вэйв'; -} diff --git a/lib/src/localization/app_ru.arb b/lib/src/localization/app_ru.arb deleted file mode 100644 index 27fe4ba..0000000 --- a/lib/src/localization/app_ru.arb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "appTitle": "Вэйв", - "@appTitle": { - "description": "Название приложения" - } -} diff --git a/lib/src/screens/foreground_switch_screen/copy_code_screen.dart b/lib/src/screens/foreground_switch_screen/copy_code_screen.dart index 757895d..38c6dc4 100644 --- a/lib/src/screens/foreground_switch_screen/copy_code_screen.dart +++ b/lib/src/screens/foreground_switch_screen/copy_code_screen.dart @@ -6,6 +6,7 @@ import 'package:md_ui_kit/md_ui_kit.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:wave_p2p/src/core/keys.dart'; import 'package:wave_p2p/src/core/webrtc_manager.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; class CopyCodeScreen extends StatefulWidget { const CopyCodeScreen({super.key, required this.onCheckPairPressed}); @@ -28,6 +29,7 @@ class _CopyCodeScreenState extends State { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); // следим за наличием ответа в менеджере — при изменении UI перестроится автоматически final manager = context.watch(); final answerReady = manager.isAnswerAvailable; @@ -36,10 +38,10 @@ class _CopyCodeScreenState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox(height: 280), - const Padding( + Padding( padding: EdgeInsets.symmetric(horizontal: 57.0), child: WaveText( - 'This is your two-word pair code. Copy and send it to your friend', + locale.translate("copy_code_screen.your_code_text"), type: WaveTextType.caption, maxLines: 3, textAlign: TextAlign.center, @@ -56,12 +58,12 @@ class _CopyCodeScreenState extends State { ), ] else ...[ // TODO change - const Text('Failed to create code'), + Text(locale.translate("copy_code_screen.fail_create_code_text")), ], const SizedBox(height: 135), // Check pair: enabled когда пришёл answer WaveSimpleButton( - label: 'Check pair', + label: locale.translate("copy_code_screen.check_button"), onPressed: answerReady ? _onButtonPressed : null, ), const SizedBox(height: 20), @@ -69,7 +71,7 @@ class _CopyCodeScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 57.0), child: WaveText( - 'Wait your friend to paste the code for button enabling', + locale.translate("copy_code_screen.wait_text"), type: WaveTextType.caption, maxLines: 3, textAlign: TextAlign.center, diff --git a/lib/src/screens/foreground_switch_screen/enable_microphone_screen.dart b/lib/src/screens/foreground_switch_screen/enable_microphone_screen.dart index 5396532..847075d 100644 --- a/lib/src/screens/foreground_switch_screen/enable_microphone_screen.dart +++ b/lib/src/screens/foreground_switch_screen/enable_microphone_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:md_ui_kit/widgets/wave_simple_button.dart'; import 'package:md_ui_kit/widgets/wave_text.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; class EnableMicrophoneScreen extends StatelessWidget { const EnableMicrophoneScreen({ @@ -12,16 +13,17 @@ class EnableMicrophoneScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ WaveText( - 'Allow access, please', + locale.translate('enable_microphone_screen.allow_access_text'), type: WaveTextType.subtitle, ), SizedBox(height: 20), WaveSimpleButton( - label: 'Mic on', + label: locale.translate('enable_microphone_screen.mic_on_button'), onPressed: onNext, showShadow: true, type: WaveButtonType.alternative, diff --git a/lib/src/screens/foreground_switch_screen/main_screen.dart b/lib/src/screens/foreground_switch_screen/main_screen.dart index 477c69d..f527d49 100644 --- a/lib/src/screens/foreground_switch_screen/main_screen.dart +++ b/lib/src/screens/foreground_switch_screen/main_screen.dart @@ -44,6 +44,7 @@ class _MainScreenState extends State { Future _getLocalOfferId() async { final prefs = await SharedPreferences.getInstance(); // TODO: обработать случай когда нет кода в локальной памяти + // TODO: что-то сделать с локалькой final id = prefs.getString(currentPeerLocalIdKey) ?? 'Invalid two-word code'; setState(() { diff --git a/lib/src/screens/foreground_switch_screen/main_screen/call_screen.dart b/lib/src/screens/foreground_switch_screen/main_screen/call_screen.dart index 0261917..70d8959 100644 --- a/lib/src/screens/foreground_switch_screen/main_screen/call_screen.dart +++ b/lib/src/screens/foreground_switch_screen/main_screen/call_screen.dart @@ -9,6 +9,7 @@ import 'package:provider/provider.dart'; import 'package:callkeep/callkeep.dart' show FlutterCallkeep; import 'package:wave_p2p/models/call_state.dart'; import 'package:wave_p2p/src/core/webrtc_manager.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; import 'package:wave_p2p/src/widgets/swipe_switcher.dart'; class CallScreen extends StatefulWidget { @@ -31,6 +32,7 @@ class _CallScreenState extends State { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); final manager = widget.disposableManager ?? Provider.of(context); @@ -43,29 +45,50 @@ class _CallScreenState extends State { // Находим локального участника (you) final localParticipant = participants.firstWhere( (p) => p.id == manager.localId, - orElse: () => - ParticipantState(id: '1', inCall: false, muted: true, name: 'You'), + orElse: () => ParticipantState( + id: '1', + inCall: false, + muted: true, + name: locale.translate("call_screen.you_text"), + ), ); // Находим удаленного участника (peer) - первого из оставшихся final remoteParticipant = participants.firstWhere( (p) => p.id != manager.localId, - orElse: () => - ParticipantState(id: '2', inCall: false, muted: true, name: 'Peer'), + orElse: () => ParticipantState( + id: '2', + inCall: false, + muted: true, + name: locale.translate("call_screen.peer_text"), + ), ); + String resolveDividerText(CallState state) { + switch (state) { + case CallState.connected: + return locale.translate("call_screen.connected_text"); + case CallState.failed: + return locale.translate("call_screen.call_failed_text"); + case CallState.connecting: + return locale.translate("call_screen.connecting_text"); + default: + return locale.translate("call_screen.ready_text"); + } + } + return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 12.0), child: WaveDivider( type: _resolveDividerType(manager.callState), - label: _resolveDividerText(manager.callState), + label: resolveDividerText(manager.callState), ), ), WaveChatBubble( type: WaveChatBubbleType.bubbleMessageInfo, - label: 'Your call is end-to-end encrypted', + label: locale.translate("call_screen.encrypted_text"), ), SizedBox(height: 40), @@ -84,8 +107,11 @@ class _CallScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 36.0), child: WaveDeviceMenu( items: mics, - subtitle: 'Current Input Device', - labelBuilder: (item) => item.label ?? 'Default Microphone', + subtitle: + locale.translate("call_screen.current_input_device_text"), + labelBuilder: (item) => + item.label ?? + locale.translate("call_screen.default_microphone_text"), onChanged: (v) => manager.selectMic(v.deviceId), ), ), @@ -94,8 +120,11 @@ class _CallScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 36.0), child: WaveDeviceMenu( items: outs, - subtitle: 'Current Output Device', - labelBuilder: (item) => item.label ?? 'Default Speaker', + subtitle: + locale.translate("call_screen.current_output_device_text"), + labelBuilder: (item) => + item.label ?? + locale.translate("call_screen.default_speaker_text"), onChanged: (v) => manager.selectSpeaker(v.deviceId), ), ), @@ -108,7 +137,7 @@ class _CallScreenState extends State { children: [ if (localParticipant != null) WaveParticipant( - label: 'You', + label: locale.translate("call_screen.you_text"), inCall: localParticipant.inCall, muted: localParticipant.muted, ), @@ -117,7 +146,7 @@ class _CallScreenState extends State { SizedBox(width: 16), if (remoteParticipant != null) WaveParticipant( - label: 'Peer', + label: locale.translate("call_screen.peer_text"), inCall: remoteParticipant.inCall, muted: remoteParticipant.muted, ), @@ -141,7 +170,7 @@ class _CallScreenState extends State { ), child: WaveCircleButton( type: WaveCircleButtonType.setting, - subtitle: 'Settings', + subtitle: locale.translate("call_screen.settings_text"), onTap: () => setState(() => isSettingsOpen = !isSettingsOpen), ), ), @@ -183,7 +212,9 @@ class _CallScreenState extends State { type: manager.inCall ? WaveCircleButtonType.leaveCall : WaveCircleButtonType.startCall, - subtitle: manager.inCall ? 'Leave Call' : 'Join Call', + subtitle: manager.inCall + ? locale.translate("call_screen.leave_call_text") + : locale.translate("call_screen.join_call_text"), onTap: () async { if (manager.inCall) { await manager.leaveCall(); @@ -266,17 +297,4 @@ class _CallScreenState extends State { return WaveDividerType.disabled; } } - - String _resolveDividerText(CallState state) { - switch (state) { - case CallState.connected: - return 'Connected'; - case CallState.failed: - return 'Call Failed'; - case CallState.connecting: - return 'Connecting...'; - default: - return 'Ready to call'; - } - } } diff --git a/lib/src/screens/foreground_switch_screen/main_screen/connection_screen.dart b/lib/src/screens/foreground_switch_screen/main_screen/connection_screen.dart index 77d49cb..3260a8b 100644 --- a/lib/src/screens/foreground_switch_screen/main_screen/connection_screen.dart +++ b/lib/src/screens/foreground_switch_screen/main_screen/connection_screen.dart @@ -5,6 +5,7 @@ import 'package:md_ui_kit/md_ui_kit.dart'; import 'package:md_ui_kit/widgets/wave_hint_text.dart' hide WaveTextType, WaveTextWeight; import 'package:wave_p2p/models/call_state.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; import 'package:wave_p2p/src/widgets/animated_status_line.dart'; class ConnectionScreen extends StatelessWidget { @@ -15,7 +16,8 @@ class ConnectionScreen extends StatelessWidget { required this.localId, required this.isPeerInitiator, required this.onReturnPressed, - required this.onClosePeerPressed, required this.state, + required this.onClosePeerPressed, + required this.state, }); final bool isNavBarShowed; @@ -28,6 +30,40 @@ class ConnectionScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); + + String resolveStatusText(CallState callState) { + switch (callState) { + case CallState.connected: + return locale.translate('connection_screen.connected_text'); + case CallState.connecting: + return locale.translate('connection_screen.connecting_text'); + case CallState.failed: + return locale.translate('connection_screen.fail_to_connect_text'); + case CallState.disconnected: + return locale.translate('connection_screen.disconnected_text'); + } + } + + String resolveSubtitleText(CallState callState, bool? isPeerInitiator) { + switch (callState) { + case CallState.connected: + return locale + .translate('connection_screen.successful_connection_text'); + case CallState.connecting: + if (isPeerInitiator == null) { + return locale.translate('connection_screen.device_to_connect_text'); + } + return isPeerInitiator + ? locale.translate('connection_screen.device_to_accept_text') + : locale.translate('connection_screen.device_to_answer_text'); + case CallState.failed: + return locale.translate('connection_screen.failed_text'); + case CallState.disconnected: + return locale.translate('connection_screen.connection_lost_text'); + } + } + return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -37,8 +73,8 @@ class ConnectionScreen extends StatelessWidget { Padding( padding: const EdgeInsets.all(20.0), child: WaveStatus( - type: _resolveStatusType(state), - label: _resolveStatusText(state), + type: resolveStatusType(state), + label: resolveStatusText(state), ), ), SizedBox(height: 20), @@ -80,9 +116,9 @@ class ConnectionScreen extends StatelessWidget { child: Row( children: [ WaveText( - _resolveSubtitleText(state, isPeerInitiator), + resolveSubtitleText(state, isPeerInitiator), type: WaveTextType.caption, - color: _resolveSubtitleColor(state), + color: resolveSubtitleColor(state), ), ], ), @@ -90,12 +126,12 @@ class ConnectionScreen extends StatelessWidget { if (state == CallState.connected) ...[ SizedBox(height: 305), WaveSimpleButton( - label: 'Close peer', + label: locale.translate('connection_screen.close_peer_button'), onPressed: onClosePeerPressed, ), SizedBox(height: 20), WaveText( - 'This leads to the termination of your connection', + locale.translate('connection_screen.warn_termination_text'), type: WaveTextType.caption, textAlign: TextAlign.center, color: MdColors.disabledColor, @@ -109,14 +145,14 @@ class ConnectionScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: WaveHintText( textAlign: TextAlign.start, - boldPart: 'This might help: ', - normalPart: - 'Return to the previous step and try to pair once again', + boldPart: locale.translate('connection_screen.help_text'), + normalPart: locale + .translate('connection_screen.return_to_prev_step_text'), ), ), SizedBox(height: 260), WaveSimpleButton( - label: 'Return', + label: locale.translate('connection_screen.return_button'), onPressed: onReturnPressed, ), SizedBox(height: 260), @@ -127,7 +163,7 @@ class ConnectionScreen extends StatelessWidget { ); } - WaveStatusType _resolveStatusType(CallState callState) { + WaveStatusType resolveStatusType(CallState callState) { switch (callState) { case CallState.connected: return WaveStatusType.positive; @@ -140,36 +176,7 @@ class ConnectionScreen extends StatelessWidget { } } - _resolveStatusText(CallState callState) { - switch (callState) { - case CallState.connected: - return 'Connected'; - case CallState.connecting: - return 'Connecting'; - case CallState.failed: - return 'Failed to connect'; - case CallState.disconnected: - return 'Disconnected'; - } - } - - String _resolveSubtitleText(CallState callState, bool? isPeerInitiator) { - switch (callState) { - case CallState.connected: - return 'Successful connection!'; - case CallState.connecting: - if (isPeerInitiator == null) return 'Waiting other device to connect..'; - return isPeerInitiator - ? 'Waiting your friend’s device to accept..' - : 'Waiting your friend’s device to answer..'; - case CallState.failed: - return 'Failed!'; - case CallState.disconnected: - return 'Connection lost!'; - } - } - - _resolveSubtitleColor(CallState callState) { + resolveSubtitleColor(CallState callState) { switch (callState) { case CallState.connected: return MdColors.positiveColor; diff --git a/lib/src/screens/foreground_switch_screen/paste_code_screen.dart b/lib/src/screens/foreground_switch_screen/paste_code_screen.dart index f9f91af..23505c0 100644 --- a/lib/src/screens/foreground_switch_screen/paste_code_screen.dart +++ b/lib/src/screens/foreground_switch_screen/paste_code_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:md_ui_kit/md_ui_kit.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:wave_p2p/src/core/keys.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; class PasteCodeScreen extends StatefulWidget { const PasteCodeScreen({ @@ -20,13 +21,14 @@ class _PasteCodeScreenState extends State { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 57.0), child: WaveText( - 'Copy your friend’s code and paste it to the text input below:', + locale.translate("paste_code_screen.paste_code_text"), type: WaveTextType.caption, maxLines: 3, textAlign: TextAlign.center, @@ -41,7 +43,7 @@ class _PasteCodeScreenState extends State { ), SizedBox(height: 135), WaveSimpleButton( - label: 'Connect', + label: locale.translate("paste_code_screen.connect_button"), onPressed: _onAcceptOfferPressed, ), ], diff --git a/lib/src/screens/foreground_switch_screen/start_connection_screen.dart b/lib/src/screens/foreground_switch_screen/start_connection_screen.dart index ee47376..d98e39c 100644 --- a/lib/src/screens/foreground_switch_screen/start_connection_screen.dart +++ b/lib/src/screens/foreground_switch_screen/start_connection_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:md_ui_kit/_core/colors.dart'; import 'package:md_ui_kit/md_ui_kit.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; class StartConnectionScreen extends StatelessWidget { const StartConnectionScreen({ @@ -19,27 +20,31 @@ class StartConnectionScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); final orWidget = Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: WaveDivider(type: WaveDividerType.disabled, label: 'OR'), + child: WaveDivider( + type: WaveDividerType.disabled, + label: locale.translate('start_connection_screen.or_text'), + ), ); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ WaveText( - 'Create a code', + locale.translate('start_connection_screen.create_code_text'), type: WaveTextType.title, weight: WaveTextWeight.bold, ), SizedBox(height: 10), WaveText( - 'If you want to initiate a connection', + locale.translate('start_connection_screen.initiate_text'), type: WaveTextType.caption, color: MdColors.disabledColor, ), SizedBox(height: 24), WaveSimpleButton( - label: 'Create', + label: locale.translate('start_connection_screen.create_button'), onPressed: onCreateCode, type: WaveButtonType.main, padding: EdgeInsets.symmetric( @@ -66,19 +71,19 @@ class StartConnectionScreen extends StatelessWidget { SizedBox(height: 80), WaveText( - 'Paste code from friend', + locale.translate('start_connection_screen.paste_code_text'), type: WaveTextType.title, weight: WaveTextWeight.bold, ), SizedBox(height: 10), WaveText( - 'If you want to connect to already created peer', + locale.translate('start_connection_screen.connect_text'), type: WaveTextType.caption, color: MdColors.disabledColor, ), SizedBox(height: 24), WaveSimpleButton( - label: 'Paste', + label: locale.translate('start_connection_screen.paste_button'), onPressed: onPasteCode, type: WaveButtonType.main, padding: EdgeInsets.symmetric( diff --git a/lib/src/screens/foreground_switch_screen/start_screen.dart b/lib/src/screens/foreground_switch_screen/start_screen.dart index fda486f..f1938b6 100644 --- a/lib/src/screens/foreground_switch_screen/start_screen.dart +++ b/lib/src/screens/foreground_switch_screen/start_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:md_ui_kit/_core/colors.dart'; import 'package:md_ui_kit/md_ui_kit.dart'; import 'package:wave_p2p/src/widgets/quad_painter.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; // где лежит extension class StartScreen extends StatefulWidget { const StartScreen({super.key, required this.onNext}); @@ -120,6 +121,7 @@ class _StartScreenState extends State @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); const double topPadding = 40 + 28; return LayoutBuilder( @@ -189,7 +191,7 @@ class _StartScreenState extends State child: Padding( padding: const EdgeInsets.all(20.0), child: WaveText( - 'Welcome!', + locale.translate('start_screen.welcome_text'), type: WaveTextType.subtitle, weight: WaveTextWeight.bold, color: MdColors.brandColor, @@ -237,7 +239,7 @@ class _StartScreenState extends State child: Padding( padding: EdgeInsets.only(top: screenH / 3 - 68), child: WaveSimpleButton( - label: 'Start', + label: locale.translate('start_screen.start_button'), onPressed: _onStartPressed, type: WaveButtonType.alternative, showShadow: true, diff --git a/lib/src/wave_app.dart b/lib/src/wave_app.dart index 9343ba8..0dd8c75 100644 --- a/lib/src/wave_app.dart +++ b/lib/src/wave_app.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; +//import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:wave_p2p/src/core/storage.dart'; import 'package:wave_p2p/src/core/webrtc_manager.dart'; -import 'package:wave_p2p/src/localization/app_localizations.dart'; +import 'package:wave_p2p/src/i18n/localizations.dart'; import 'package:wave_p2p/src/screens/initial_screen_impl.dart'; // import 'sample_feature/sample_item_details_view.dart'; @@ -11,7 +12,6 @@ import 'package:wave_p2p/src/screens/initial_screen_impl.dart'; import 'settings/settings_controller.dart'; import 'settings/settings_view.dart'; - /// The Widget that configures your application. class WaveApp extends StatelessWidget { const WaveApp({ @@ -38,59 +38,60 @@ class WaveApp extends StatelessWidget { listenable: settingsController, builder: (BuildContext context, Widget? child) { return MaterialApp( - debugShowCheckedModeBanner: false, - // Providing a restorationScopeId allows the Navigator built by the - // MaterialApp to restore the navigation stack when a user leaves and - // returns to the app after it has been killed while running in the - // background. - restorationScopeId: 'app', + debugShowCheckedModeBanner: false, + // Providing a restorationScopeId allows the Navigator built by the + // MaterialApp to restore the navigation stack when a user leaves and + // returns to the app after it has been killed while running in the + // background. + restorationScopeId: 'app', - // Provide the generated AppLocalizations to the MaterialApp. This - // allows descendant Widgets to display the correct translations - // depending on the user's locale. - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('ru', ''), // English, no country code - ], + // Provide the generated AppLocalizations to the MaterialApp. This + // allows descendant Widgets to display the correct translations + // depending on the user's locale. + localizationsDelegates: const [ + AppLocalisationDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('ru'), + Locale('en'), + ], - // Use AppLocalizations to configure the correct application title - // depending on the user's locale. - // - // The appTitle is defined in .arb files found in the localization - // directory. - onGenerateTitle: (BuildContext context) => - AppLocalizations.of(context)!.appTitle, + // Use AppLocalizations to configure the correct application title + // depending on the user's locale. + // + // The appTitle is defined in .arb files found in the localization + // directory. + // onGenerateTitle: (BuildContext context) => + // AppLocalizations.of(context)!.appTitle, - // Define a light and dark color theme. Then, read the user's - // preferred ThemeMode (light, dark, or system default) from the - // SettingsController to display the correct theme. - theme: ThemeData(), - darkTheme: ThemeData.dark(), - themeMode: settingsController.themeMode, + // Define a light and dark color theme. Then, read the user's + // preferred ThemeMode (light, dark, or system default) from the + // SettingsController to display the correct theme. + theme: ThemeData(), + darkTheme: ThemeData.dark(), + themeMode: settingsController.themeMode, - // Define a function to handle named routes in order to support - // Flutter web url navigation and deep linking. - onGenerateRoute: (RouteSettings routeSettings) { - return MaterialPageRoute( - settings: routeSettings, - builder: (BuildContext context) { - switch (routeSettings.name) { - case SettingsView.routeName: - return SettingsView(controller: settingsController); - // case SampleItemDetailsView.routeName: - // return const SampleItemDetailsView(); - // case SampleItemListView.routeName: - default: - return const InitialScreenImpl(); - } - }, - ); - }, + // Define a function to handle named routes in order to support + // Flutter web url navigation and deep linking. + onGenerateRoute: (RouteSettings routeSettings) { + return MaterialPageRoute( + settings: routeSettings, + builder: (BuildContext context) { + switch (routeSettings.name) { + case SettingsView.routeName: + return SettingsView(controller: settingsController); + // case SampleItemDetailsView.routeName: + // return const SampleItemDetailsView(); + // case SampleItemListView.routeName: + default: + return const InitialScreenImpl(); + } + }, + ); + }, ); }, ), diff --git a/pubspec.lock b/pubspec.lock index faa64c9..7b5618a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: build - sha256: "5b887c55a0f734b433b3b2d89f9cd1f99eb636b17e268a5b4259258bc916504b" + sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.2" build_config: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: "direct main" description: name: build_runner - sha256: "804c47c936df75e1911c19a4fb8c46fa8ff2b3099b9f2b2aa4726af3774f734b" + sha256: "4e54dbeefdc70691ba80b3bce3976af63b5425c8c07dface348dfee664a0edc1" url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.9.0" built_collection: dependency: transitive description: @@ -405,14 +405,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -454,7 +446,7 @@ packages: source: hosted version: "4.1.2" intl: - dependency: transitive + dependency: "direct main" description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" @@ -521,10 +513,10 @@ packages: dependency: transitive description: name: logger - sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.6.2" logging: dependency: transitive description: @@ -626,10 +618,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" url: "https://pub.dev" source: hosted - version: "2.2.18" + version: "2.2.19" path_provider_foundation: dependency: transitive description: @@ -786,26 +778,26 @@ packages: dependency: transitive description: name: record - sha256: "9dbc6ff3e784612f90a9b001373c45ff76b7a08abd2bd9fdf72c242320c8911c" + sha256: "6bad72fb3ea6708d724cf8b6c97c4e236cf9f43a52259b654efeb6fd9b737f1f" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.2" record_android: dependency: transitive description: name: record_android - sha256: "854627cd78d8d66190377f98477eee06ca96ab7c9f2e662700daf33dbf7e6673" + sha256: f05677eeed074898327f890f232f9eb49cd99d1c20d0daaf22b5612f4b2301bb url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.4.3" record_ios: dependency: transitive description: name: record_ios - sha256: "13e241ed9cbc220534a40ae6b66222e21288db364d96dd66fb762ebd3cb77c71" + sha256: "765b42ac1be019b1674ddd809b811fc721fe5a93f7bb1da7803f0d16772fd6d7" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.4" record_linux: dependency: transitive description: @@ -818,10 +810,10 @@ packages: dependency: transitive description: name: record_macos - sha256: "2849068bb59072f300ad63ed146e543d66afaef8263edba4de4834fc7c8d4d35" + sha256: "842ea4b7e95f4dd237aacffc686d1b0ff4277e3e5357865f8d28cd28bc18ed95" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" record_platform_interface: dependency: transitive description: @@ -1063,10 +1055,10 @@ packages: dependency: transitive description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.4" wave: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 0eaba63..1291ee3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: uuid: ^4.5.1 callkeep: ^0.4.1 wave: ^0.2.2 + intl: ^0.20.2 # redux: ^5.0.0 # flutter_redux: ^0.10.0 @@ -60,6 +61,7 @@ flutter: - assets/icons/mic/ - assets/icons/circle_button/ - assets/icons/participants/ + - assets/i18n/ fonts: - family: Play