Change prefixes and add check_strings.py

This commit is contained in:
Dain Nilsson 2023-03-02 12:45:55 +01:00
parent fd1046eb72
commit b33dca3900
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
46 changed files with 456 additions and 340 deletions

110
check_strings.py Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python3
# Copyright (C) 2023 Yubico.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import json
errors = []
def check_duplicate_keys(pairs):
seen = set()
for d in [k for k, v in pairs if k in seen or seen.add(k)]:
errors.append(f"Duplicate key: {d}")
return dict(pairs)
def check_duplicate_values(strings):
seen = {}
for k, v in strings.items():
if isinstance(v, str):
if v in seen:
errors.append(
f"Duplicate value in key: {k} (originally in {seen[v]}): {v}"
)
else:
seen[v] = k
def check_prefixes(k, v, s_max_words, s_max_len):
errs = []
if k.startswith("s_"):
if len(v) > s_max_len:
errs.append(f"Too long ({len(v)} chars)")
if len(v.split()) > s_max_words:
errs.append(f"Too many words ({len(v.split())})")
elif k.startswith("l_"):
if v.endswith("."):
errs.append("Ends with '.'")
if ". " in v:
errs.append("Spans multiple sentences")
elif k.startswith("p_"):
if v[-1] not in ".!":
errs.append("Doesn't end in punctuation")
elif k.startswith("q_"):
if not v.endswith("?"):
errs.append("Doesn't end in '?'")
return errs
def check_misc(k, v):
errs = []
if "..." in v:
errs.append("'...' should be replaced with '\\u2026'")
return errs
def lint_strings(strings, rules):
for k, v in strings.items():
errs = []
errs.extend(
check_prefixes(
k,
v,
rules.get("s_max_words", 4),
rules.get("s_max_len", 32),
)
)
errs.extend(check_misc(k, v))
if errs:
errors.append(f'Errors in {k}: "{v}"')
errors.extend([f" {e}" for e in errs])
if len(sys.argv) != 2:
print("USAGE: check_strings.py <ARB_FILE>")
sys.exit(1)
target = sys.argv[1]
with open(target) as f:
values = json.load(f, object_pairs_hook=check_duplicate_keys)
strings = {k: v for k, v in values.items() if not k.startswith("@")}
check_duplicate_values(strings)
lint_strings(strings, strings.get("@_lint_rules", {}))
print(len(strings), "strings in file")
if errors:
for e in errors:
print(e)
sys.exit(1)
print("OK")

View File

@ -42,7 +42,7 @@ class AboutPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.w_about), title: Text(l10n.s_about),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 32), padding: const EdgeInsets.symmetric(vertical: 32),
child: Column( child: Column(
@ -63,7 +63,7 @@ class AboutPage extends ConsumerWidget {
children: [ children: [
TextButton( TextButton(
child: Text( child: Text(
l10n.l_terms_of_use, l10n.s_terms_of_use,
style: style:
const TextStyle(decoration: TextDecoration.underline), const TextStyle(decoration: TextDecoration.underline),
), ),
@ -73,7 +73,7 @@ class AboutPage extends ConsumerWidget {
), ),
TextButton( TextButton(
child: Text( child: Text(
l10n.l_privacy_policy, l10n.s_privacy_policy,
style: style:
const TextStyle(decoration: TextDecoration.underline), const TextStyle(decoration: TextDecoration.underline),
), ),
@ -85,7 +85,7 @@ class AboutPage extends ConsumerWidget {
), ),
TextButton( TextButton(
child: Text( child: Text(
l10n.l_open_src_licenses, l10n.s_open_src_licenses,
style: const TextStyle(decoration: TextDecoration.underline), style: const TextStyle(decoration: TextDecoration.underline),
), ),
onPressed: () { onPressed: () {
@ -104,7 +104,7 @@ class AboutPage extends ConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text( child: Text(
l10n.l_help_and_feedback, l10n.s_help_and_feedback,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
), ),
@ -113,7 +113,7 @@ class AboutPage extends ConsumerWidget {
children: [ children: [
TextButton( TextButton(
child: Text( child: Text(
l10n.l_send_feedback, l10n.s_send_feedback,
style: style:
const TextStyle(decoration: TextDecoration.underline), const TextStyle(decoration: TextDecoration.underline),
), ),
@ -123,7 +123,7 @@ class AboutPage extends ConsumerWidget {
), ),
TextButton( TextButton(
child: Text( child: Text(
l10n.l_i_need_help, l10n.s_i_need_help,
style: style:
const TextStyle(decoration: TextDecoration.underline), const TextStyle(decoration: TextDecoration.underline),
), ),
@ -140,7 +140,7 @@ class AboutPage extends ConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text( child: Text(
l10n.w_troubleshooting, l10n.s_troubleshooting,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
), ),
@ -151,7 +151,7 @@ class AboutPage extends ConsumerWidget {
const SizedBox(height: 12.0), const SizedBox(height: 12.0),
ActionChip( ActionChip(
avatar: const Icon(Icons.bug_report_outlined), avatar: const Icon(Icons.bug_report_outlined),
label: Text(l10n.l_run_diagnostics), label: Text(l10n.s_run_diagnostics),
onPressed: () async { onPressed: () async {
_log.info('Running diagnostics...'); _log.info('Running diagnostics...');
final response = await ref final response = await ref
@ -180,7 +180,7 @@ class AboutPage extends ConsumerWidget {
if (isAndroid) ...[ if (isAndroid) ...[
const SizedBox(height: 12.0), const SizedBox(height: 12.0),
FilterChip( FilterChip(
label: Text(l10n.l_allow_screenshots), label: Text(l10n.s_allow_screenshots),
selected: ref.watch(androidAllowScreenshotsProvider), selected: ref.watch(androidAllowScreenshotsProvider),
onSelected: (value) async { onSelected: (value) async {
ref ref
@ -216,7 +216,7 @@ class LoggingPanel extends ConsumerWidget {
value: logLevel, value: logLevel,
items: Levels.LEVELS, items: Levels.LEVELS,
selected: logLevel != Level.INFO, selected: logLevel != Level.INFO,
labelBuilder: (value) => Text(l10n.l_log_level( labelBuilder: (value) => Text(l10n.s_log_level(
value.name[0] + value.name.substring(1).toLowerCase())), value.name[0] + value.name.substring(1).toLowerCase())),
itemBuilder: (value) => itemBuilder: (value) =>
Text('${value.name[0]}${value.name.substring(1).toLowerCase()}'), Text('${value.name[0]}${value.name.substring(1).toLowerCase()}'),
@ -227,7 +227,7 @@ class LoggingPanel extends ConsumerWidget {
), ),
ActionChip( ActionChip(
avatar: const Icon(Icons.copy), avatar: const Icon(Icons.copy),
label: Text(l10n.l_copy_log), label: Text(l10n.s_copy_log),
onPressed: () async { onPressed: () async {
_log.info('Copying log to clipboard ($version)...'); _log.info('Copying log to clipboard ($version)...');
final logs = await ref.read(logLevelProvider.notifier).getLogs(); final logs = await ref.read(logLevelProvider.notifier).getLogs();

View File

@ -195,7 +195,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
return promptUserInteraction( return promptUserInteraction(
context, context,
icon: const Icon(Icons.touch_app), icon: const Icon(Icons.touch_app),
title: l10n.l_touch_required, title: l10n.s_touch_required,
description: l10n.l_touch_button_now, description: l10n.l_touch_button_now,
); );
}, },

View File

@ -75,7 +75,7 @@ class QRScannerPermissionsUI extends StatelessWidget {
Navigator.of(context).pop(''); Navigator.of(context).pop('');
}, },
child: Text( child: Text(
l10n.l_enter_manually, l10n.s_enter_manually,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
)), )),
], ],
@ -92,7 +92,7 @@ class QRScannerPermissionsUI extends StatelessWidget {
onPermissionRequest(); onPermissionRequest();
}, },
child: Text( child: Text(
l10n.l_review_permissions, l10n.s_review_permissions,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
)), )),
], ],

View File

@ -73,7 +73,7 @@ class QRScannerUI extends StatelessWidget {
}, },
key: keys.manualEntryButton, key: keys.manualEntryButton,
child: Text( child: Text(
l10n.l_enter_manually, l10n.s_enter_manually,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
)), )),
], ],

View File

@ -114,7 +114,7 @@ class _QrScannerViewState extends State<QrScannerView> {
extendBody: true, extendBody: true,
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
l10n.l_add_account, l10n.s_add_account,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,

View File

@ -80,11 +80,11 @@ extension on ThemeMode {
String getDisplayName(AppLocalizations l10n) { String getDisplayName(AppLocalizations l10n) {
switch (this) { switch (this) {
case ThemeMode.system: case ThemeMode.system:
return l10n.l_system_default; return l10n.s_system_default;
case ThemeMode.light: case ThemeMode.light:
return l10n.l_light_mode; return l10n.s_light_mode;
case ThemeMode.dark: case ThemeMode.dark:
return l10n.l_dark_mode; return l10n.s_dark_mode;
} }
} }
} }
@ -114,7 +114,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
final theme = Theme.of(context); final theme = Theme.of(context);
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.w_settings), title: Text(l10n.s_settings),
child: Theme( child: Theme(
// Make the headers use the primary color to pop a bit. // Make the headers use the primary color to pop a bit.
// Once M3 is implemented this will probably not be needed. // Once M3 is implemented this will probably not be needed.
@ -127,7 +127,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTitle(l10n.l_nfc_options), ListTitle(l10n.s_nfc_options),
ListTile( ListTile(
title: Text(l10n.l_on_yk_nfc_tap), title: Text(l10n.l_on_yk_nfc_tap),
subtitle: Text(tapAction.getDescription(l10n)), subtitle: Text(tapAction.getDescription(l10n)),
@ -166,7 +166,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
}); });
}), }),
SwitchListTile( SwitchListTile(
title: Text(l10n.l_silence_nfc_sounds), title: Text(l10n.s_silence_nfc_sounds),
subtitle: Text(nfcSilenceSounds subtitle: Text(nfcSilenceSounds
? l10n.l_silence_nfc_sounds_on ? l10n.l_silence_nfc_sounds_on
: l10n.l_silence_nfc_sounds_off), : l10n.l_silence_nfc_sounds_off),
@ -177,7 +177,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
prefs.setBool(prefNfcSilenceSounds, value); prefs.setBool(prefNfcSilenceSounds, value);
}); });
}), }),
ListTitle(l10n.l_usb_options), ListTitle(l10n.s_usb_options),
SwitchListTile( SwitchListTile(
title: Text(l10n.l_launch_app_on_usb), title: Text(l10n.l_launch_app_on_usb),
subtitle: Text(usbOpenApp subtitle: Text(usbOpenApp
@ -190,9 +190,9 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
prefs.setBool(prefUsbOpenApp, value); prefs.setBool(prefUsbOpenApp, value);
}); });
}), }),
ListTitle(l10n.w_appearance), ListTitle(l10n.s_appearance),
ListTile( ListTile(
title: Text(l10n.l_app_theme), title: Text(l10n.s_app_theme),
subtitle: Text(themeMode.getDisplayName(l10n)), subtitle: Text(themeMode.getDisplayName(l10n)),
key: keys.themeModeSetting, key: keys.themeModeSetting,
onTap: () async { onTap: () async {
@ -239,7 +239,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
builder: (BuildContext context) { builder: (BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return SimpleDialog( return SimpleDialog(
title: Text(l10n.l_choose_kbd_layout), title: Text(l10n.s_choose_kbd_layout),
children: _keyboardLayouts children: _keyboardLayouts
.map( .map(
(e) => RadioListTile<String>( (e) => RadioListTile<String>(
@ -264,7 +264,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
builder: (BuildContext context) { builder: (BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return SimpleDialog( return SimpleDialog(
title: Text(l10n.l_choose_app_theme), title: Text(l10n.s_choose_app_theme),
children: supportedThemes children: supportedThemes
.map((e) => RadioListTile( .map((e) => RadioListTile(
title: Text(e.getDisplayName(l10n)), title: Text(e.getDisplayName(l10n)),

View File

@ -62,9 +62,9 @@ enum Application {
String getDisplayName(AppLocalizations l10n) { String getDisplayName(AppLocalizations l10n) {
switch (this) { switch (this) {
case Application.oath: case Application.oath:
return l10n.w_authenticator; return l10n.s_authenticator;
case Application.fido: case Application.fido:
return l10n.w_webauthn; return l10n.s_webauthn;
default: default:
return name.substring(0, 1).toUpperCase() + name.substring(1); return name.substring(0, 1).toUpperCase() + name.substring(1);
} }

View File

@ -48,7 +48,7 @@ class AppFailurePage extends ConsumerWidget {
case 'ccid': case 'ccid':
header = l10n.l_ccid_connection_failed; header = l10n.l_ccid_connection_failed;
if (Platform.isMacOS) { if (Platform.isMacOS) {
message = l10n.l_try_reinsert_yk; message = l10n.p_try_reinsert_yk;
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
message = l10n.p_pcscd_unavailable; message = l10n.p_pcscd_unavailable;
} else { } else {
@ -63,7 +63,7 @@ class AppFailurePage extends ConsumerWidget {
message = l10n.p_webauthn_elevated_permissions_required; message = l10n.p_webauthn_elevated_permissions_required;
actions = [ actions = [
ElevatedButton.icon( ElevatedButton.icon(
label: Text(l10n.w_unlock), label: Text(l10n.s_unlock),
icon: const Icon(Icons.lock_open), icon: const Icon(Icons.lock_open),
onPressed: () async { onPressed: () async {
final closeMessage = showMessage( final closeMessage = showMessage(
@ -77,7 +77,7 @@ class AppFailurePage extends ConsumerWidget {
(context) async { (context) async {
showMessage( showMessage(
context, context,
l10n.l_permission_denied, l10n.s_permission_denied,
); );
}, },
); );
@ -92,7 +92,7 @@ class AppFailurePage extends ConsumerWidget {
break; break;
default: default:
header = l10n.l_open_connection_failed; header = l10n.l_open_connection_failed;
message = l10n.l_try_reinsert_yk; message = l10n.p_try_reinsert_yk;
} }
} }
} }

View File

@ -129,7 +129,7 @@ class AppPage extends StatelessWidget {
}, },
icon: const Icon(Icons.tune), icon: const Icon(Icons.tune),
iconSize: 24, iconSize: 24,
tooltip: AppLocalizations.of(context)!.l_configure_yk, tooltip: AppLocalizations.of(context)!.s_configure_yk,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
), ),
), ),

View File

@ -45,7 +45,7 @@ class DeviceButton extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return IconButton( return IconButton(
tooltip: AppLocalizations.of(context)!.l_select_yk, tooltip: AppLocalizations.of(context)!.s_select_yk,
icon: _CircledDeviceAvatar(radius), icon: _CircledDeviceAvatar(radius),
onPressed: () async { onPressed: () async {
await showBlurDialog( await showBlurDialog(

View File

@ -44,7 +44,7 @@ class DeviceErrorScreen extends ConsumerWidget {
message: l10n.p_elevated_permissions_required, message: l10n.p_elevated_permissions_required,
actions: [ actions: [
ElevatedButton.icon( ElevatedButton.icon(
label: Text(l10n.w_unlock), label: Text(l10n.s_unlock),
icon: const Icon(Icons.lock_open), icon: const Icon(Icons.lock_open),
onPressed: () async { onPressed: () async {
final closeMessage = showMessage( final closeMessage = showMessage(
@ -55,7 +55,7 @@ class DeviceErrorScreen extends ConsumerWidget {
ref.invalidate(rpcProvider); ref.invalidate(rpcProvider);
} else { } else {
await ref.read(withContextProvider)((context) async => await ref.read(withContextProvider)((context) async =>
showMessage(context, l10n.l_permission_denied)); showMessage(context, l10n.s_permission_denied));
} }
} finally { } finally {
closeMessage(); closeMessage();
@ -81,7 +81,7 @@ class DeviceErrorScreen extends ConsumerWidget {
final String message; final String message;
switch (error) { switch (error) {
case 'unknown-device': case 'unknown-device':
message = l10n.l_unknown_device; message = l10n.s_unknown_device;
break; break;
default: default:
message = l10n.l_place_on_nfc_reader; message = l10n.l_place_on_nfc_reader;

View File

@ -122,7 +122,7 @@ class _DevicePickerContent extends ConsumerWidget {
title: Center(child: Text(l10n.l_no_yk_present)), title: Center(child: Text(l10n.l_no_yk_present)),
subtitle: Center( subtitle: Center(
child: Text( child: Text(
Platform.isAndroid ? l10n.l_insert_or_tap_yk : l10n.w_usb)), Platform.isAndroid ? l10n.l_insert_or_tap_yk : l10n.s_usb)),
), ),
], ],
); );
@ -136,7 +136,7 @@ class _DevicePickerContent extends ConsumerWidget {
padding: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.symmetric(horizontal: 4),
child: DeviceAvatar(child: Icon(Icons.usb)), child: DeviceAvatar(child: Icon(Icons.usb)),
), ),
title: Text(l10n.w_usb), title: Text(l10n.s_usb),
subtitle: Text(l10n.l_no_yk_present), subtitle: Text(l10n.l_no_yk_present),
onTap: () { onTap: () {
ref.read(currentDeviceProvider.notifier).setCurrentDevice(null); ref.read(currentDeviceProvider.notifier).setCurrentDevice(null);
@ -168,7 +168,7 @@ class _DevicePickerContent extends ConsumerWidget {
ref.read(_hiddenDevicesProvider.notifier).showAll(); ref.read(_hiddenDevicesProvider.notifier).showAll();
}, },
child: ListTile( child: ListTile(
title: Text(l10n.l_show_hidden_devices), title: Text(l10n.s_show_hidden_devices),
dense: true, dense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),
@ -195,11 +195,11 @@ String _getDeviceInfoString(BuildContext context, DeviceInfo info) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final serial = info.serial; final serial = info.serial;
return [ return [
if (serial != null) l10n.l_sn_serial(serial), if (serial != null) l10n.s_sn_serial(serial),
if (info.version.isAtLeast(1)) if (info.version.isAtLeast(1))
l10n.l_fw_version(info.version) l10n.s_fw_version(info.version)
else else
l10n.l_unknown_type, l10n.s_unknown_type,
].join(' '); ].join(' ');
} }
@ -211,9 +211,9 @@ List<String> _getDeviceStrings(
error: (error, _) { error: (error, _) {
switch (error) { switch (error) {
case 'device-inaccessible': case 'device-inaccessible':
return [node.name, l10n.l_yk_inaccessible]; return [node.name, l10n.s_yk_inaccessible];
case 'unknown-device': case 'unknown-device':
return [l10n.l_unknown_device]; return [l10n.s_unknown_device];
} }
return null; return null;
}, },
@ -306,9 +306,9 @@ class _DeviceRow extends ConsumerWidget {
subtitle: Text( subtitle: Text(
node.when( node.when(
usbYubiKey: (_, __, ___, info) => info == null usbYubiKey: (_, __, ___, info) => info == null
? l10n.l_yk_inaccessible ? l10n.s_yk_inaccessible
: _getDeviceInfoString(context, info), : _getDeviceInfoString(context, info),
nfcReader: (_, __) => l10n.l_select_to_scan, nfcReader: (_, __) => l10n.s_select_to_scan,
), ),
), ),
onTap: () { onTap: () {
@ -344,7 +344,7 @@ class _NfcDeviceRow extends ConsumerWidget {
ref.read(_hiddenDevicesProvider.notifier).showAll(); ref.read(_hiddenDevicesProvider.notifier).showAll();
}, },
child: ListTile( child: ListTile(
title: Text(l10n.l_show_hidden_devices), title: Text(l10n.s_show_hidden_devices),
dense: true, dense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
enabled: hidden.isNotEmpty, enabled: hidden.isNotEmpty,
@ -355,7 +355,7 @@ class _NfcDeviceRow extends ConsumerWidget {
ref.read(_hiddenDevicesProvider.notifier).hideDevice(node.path); ref.read(_hiddenDevicesProvider.notifier).hideDevice(node.path);
}, },
child: ListTile( child: ListTile(
title: Text(l10n.l_hide_device), title: Text(l10n.s_hide_device),
dense: true, dense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),

View File

@ -142,7 +142,7 @@ class MainPageDrawer extends ConsumerWidget {
NavigationDrawerDestination( NavigationDrawerDestination(
key: managementAppDrawer, key: managementAppDrawer,
label: Text( label: Text(
l10n.l_toggle_applications, l10n.s_toggle_applications,
), ),
icon: Icon(Application.management._icon), icon: Icon(Application.management._icon),
selectedIcon: Icon(Application.management._filledIcon), selectedIcon: Icon(Application.management._filledIcon),
@ -152,11 +152,11 @@ class MainPageDrawer extends ConsumerWidget {
], ],
// Non-YubiKey pages // Non-YubiKey pages
NavigationDrawerDestination( NavigationDrawerDestination(
label: Text(l10n.w_settings), label: Text(l10n.s_settings),
icon: const Icon(Icons.settings_outlined), icon: const Icon(Icons.settings_outlined),
), ),
NavigationDrawerDestination( NavigationDrawerDestination(
label: Text(l10n.l_help_and_about), label: Text(l10n.s_help_and_about),
icon: const Icon(Icons.help_outline), icon: const Icon(Icons.help_outline),
), ),
], ],

View File

@ -88,7 +88,7 @@ class MainPage extends ConsumerWidget {
actions: [ actions: [
if (hasNfcSupport && !isNfcEnabled) if (hasNfcSupport && !isNfcEnabled)
ElevatedButton.icon( ElevatedButton.icon(
label: Text(l10n.l_enable_nfc), label: Text(l10n.s_enable_nfc),
icon: nfcIcon, icon: nfcIcon,
onPressed: () async { onPressed: () async {
await openNfcSettings(); await openNfcSettings();
@ -96,7 +96,7 @@ class MainPage extends ConsumerWidget {
], ],
actionButtonBuilder: (context) => IconButton( actionButtonBuilder: (context) => IconButton(
icon: const Icon(Icons.person_add_alt_1), icon: const Icon(Icons.person_add_alt_1),
tooltip: l10n.l_add_account, tooltip: l10n.s_add_account,
onPressed: () async { onPressed: () async {
CredentialData? otpauth; CredentialData? otpauth;
final scanner = ref.read(qrScannerProvider); final scanner = ref.read(qrScannerProvider);
@ -142,17 +142,17 @@ class MainPage extends ConsumerWidget {
if (data.info.supportedCapabilities.isEmpty && if (data.info.supportedCapabilities.isEmpty &&
data.name == 'Unrecognized device') { data.name == 'Unrecognized device') {
return MessagePage( return MessagePage(
header: l10n.l_yk_not_recognized, header: l10n.s_yk_not_recognized,
); );
} else if (app.getAvailability(data) == } else if (app.getAvailability(data) ==
Availability.unsupported) { Availability.unsupported) {
return MessagePage( return MessagePage(
header: l10n.l_app_not_supported, header: l10n.s_app_not_supported,
message: l10n.l_app_not_supported_on_yk(app.name), message: l10n.l_app_not_supported_on_yk(app.name),
); );
} else if (app.getAvailability(data) != Availability.enabled) { } else if (app.getAvailability(data) != Availability.enabled) {
return MessagePage( return MessagePage(
header: l10n.l_app_disabled, header: l10n.s_app_disabled,
message: l10n.l_app_disabled_desc(app.name), message: l10n.l_app_disabled_desc(app.name),
); );
} }
@ -164,7 +164,7 @@ class MainPage extends ConsumerWidget {
return FidoScreen(data); return FidoScreen(data);
default: default:
return MessagePage( return MessagePage(
header: l10n.l_app_not_supported, header: l10n.s_app_not_supported,
message: l10n.l_app_not_supported_desc, message: l10n.l_app_not_supported_desc,
); );
} }

View File

@ -285,7 +285,7 @@ class _HelperWaiterState extends ConsumerState<_HelperWaiter> {
actions: [ actions: [
ActionChip( ActionChip(
avatar: const Icon(Icons.copy), avatar: const Icon(Icons.copy),
label: Text(l10n.l_copy_log), label: Text(l10n.s_copy_log),
onPressed: () async { onPressed: () async {
_log.info('Copying log to clipboard ($version)...'); _log.info('Copying log to clipboard ($version)...');
final logs = await ref.read(logLevelProvider.notifier).getLogs(); final logs = await ref.read(logLevelProvider.notifier).getLogs();

View File

@ -271,7 +271,7 @@ class DesktopCredentialListNotifier extends OathCredentialListNotifier {
return promptUserInteraction( return promptUserInteraction(
context, context,
icon: const Icon(Icons.touch_app), icon: const Icon(Icons.touch_app),
title: l10n.l_touch_required, title: l10n.s_touch_required,
description: l10n.l_touch_button_now, description: l10n.l_touch_button_now,
headless: headless, headless: headless,
); );

View File

@ -176,7 +176,7 @@ class _Systray extends TrayListener {
.read(clipboardProvider) .read(clipboardProvider)
.setText(code.value, isSensitive: true); .setText(code.value, isSensitive: true);
final notification = LocalNotification( final notification = LocalNotification(
title: _l10n.l_code_copied, title: _l10n.s_code_copied,
body: _l10n.p_target_copied_clipboard(label), body: _l10n.p_target_copied_clipboard(label),
silent: true, silent: true,
); );
@ -190,12 +190,12 @@ class _Systray extends TrayListener {
), ),
if (_credentials.isEmpty) if (_credentials.isEmpty)
MenuItem( MenuItem(
label: _l10n.l_no_pinned_accounts, label: _l10n.s_no_pinned_accounts,
disabled: true, disabled: true,
), ),
MenuItem.separator(), MenuItem.separator(),
MenuItem( MenuItem(
label: _isHidden ? _l10n.l_show_window : _l10n.l_hide_window, label: _isHidden ? _l10n.s_show_window : _l10n.s_hide_window,
onClick: (_) { onClick: (_) {
_ref _ref
.read(desktopWindowStateProvider.notifier) .read(desktopWindowStateProvider.notifier)
@ -204,7 +204,7 @@ class _Systray extends TrayListener {
), ),
MenuItem.separator(), MenuItem.separator(),
MenuItem( MenuItem(
label: _l10n.w_quit, label: _l10n.s_quit,
onClick: (_) { onClick: (_) {
_ref.read(withContextProvider)( _ref.read(withContextProvider)(
(context) async { (context) async {

View File

@ -25,7 +25,7 @@ class ErrorPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.l_application_error), title: Text(AppLocalizations.of(context)!.s_application_error),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -146,7 +146,7 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
.renameFingerprint(_fingerprint!, _label); .renameFingerprint(_fingerprint!, _label);
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
showMessage(context, l10n.l_fingerprint_added); showMessage(context, l10n.s_fingerprint_added);
} catch (e) { } catch (e) {
final String errorMessage; final String errorMessage;
// TODO: Make this cleaner than importing desktop specific RpcError. // TODO: Make this cleaner than importing desktop specific RpcError.
@ -169,7 +169,7 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
final progress = _samples == 0 ? 0.0 : _samples / (_samples + _remaining); final progress = _samples == 0 ? 0.0 : _samples / (_samples + _remaining);
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_add_fingerprint), title: Text(l10n.s_add_fingerprint),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0), padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column( child: Column(
@ -208,7 +208,7 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
decoration: InputDecoration( decoration: InputDecoration(
enabled: _fingerprint != null, enabled: _fingerprint != null,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.w_name, labelText: l10n.s_name,
prefixIcon: const Icon(Icons.fingerprint_outlined), prefixIcon: const Icon(Icons.fingerprint_outlined),
), ),
onChanged: (value) { onChanged: (value) {
@ -234,7 +234,7 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
actions: [ actions: [
TextButton( TextButton(
onPressed: _fingerprint != null && _label.isNotEmpty ? _submit : null, onPressed: _fingerprint != null && _label.isNotEmpty ? _submit : null,
child: Text(l10n.w_save), child: Text(l10n.s_save),
), ),
], ],
); );

View File

@ -38,7 +38,7 @@ class DeleteCredentialDialog extends ConsumerWidget {
final label = credential.userName; final label = credential.userName;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_delete_credential), title: Text(l10n.s_delete_credential),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0), padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column( child: Column(
@ -63,11 +63,11 @@ class DeleteCredentialDialog extends ConsumerWidget {
await ref.read(withContextProvider)( await ref.read(withContextProvider)(
(context) async { (context) async {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
showMessage(context, l10n.l_credential_deleted); showMessage(context, l10n.s_credential_deleted);
}, },
); );
}, },
child: Text(l10n.w_delete), child: Text(l10n.s_delete),
), ),
], ],
); );

View File

@ -36,7 +36,7 @@ class DeleteFingerprintDialog extends ConsumerWidget {
final label = fingerprint.label; final label = fingerprint.label;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_delete_fingerprint), title: Text(l10n.s_delete_fingerprint),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@ -45,10 +45,10 @@ class DeleteFingerprintDialog extends ConsumerWidget {
.deleteFingerprint(fingerprint); .deleteFingerprint(fingerprint);
await ref.read(withContextProvider)((context) async { await ref.read(withContextProvider)((context) async {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
showMessage(context, l10n.l_fingerprint_deleted); showMessage(context, l10n.s_fingerprint_deleted);
}); });
}, },
child: Text(l10n.w_delete), child: Text(l10n.s_delete),
), ),
], ],
child: Padding( child: Padding(

View File

@ -37,7 +37,7 @@ class FidoScreen extends ConsumerWidget {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ref.watch(fidoStateProvider(deviceData.node.path)).when( return ref.watch(fidoStateProvider(deviceData.node.path)).when(
loading: () => AppPage( loading: () => AppPage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
centered: true, centered: true,
delayedContent: true, delayedContent: true,
child: const CircularProgressIndicator(), child: const CircularProgressIndicator(),
@ -48,7 +48,7 @@ class FidoScreen extends ConsumerWidget {
0; 0;
if (Capability.fido2.value & supported == 0) { if (Capability.fido2.value & supported == 0) {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: manageAccounts, graphic: manageAccounts,
header: l10n.l_ready_to_use, header: l10n.l_ready_to_use,
message: l10n.l_register_sk_on_websites, message: l10n.l_register_sk_on_websites,
@ -59,14 +59,14 @@ class FidoScreen extends ConsumerWidget {
0; 0;
if (Capability.fido2.value & enabled == 0) { if (Capability.fido2.value & enabled == 0) {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
header: l10n.l_fido_disabled, header: l10n.s_fido_disabled,
message: l10n.l_webauthn_req_fido2, message: l10n.l_webauthn_req_fido2,
); );
} }
return AppFailurePage( return AppFailurePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
cause: error, cause: error,
); );
}, },

View File

@ -32,11 +32,11 @@ Widget fidoBuildActions(
return SimpleDialog( return SimpleDialog(
children: [ children: [
if (state.bioEnroll != null) ...[ if (state.bioEnroll != null) ...[
ListTitle(l10n.w_setup, ListTitle(l10n.s_setup,
textStyle: Theme.of(context).textTheme.bodyLarge), textStyle: Theme.of(context).textTheme.bodyLarge),
ListTile( ListTile(
leading: const CircleAvatar(child: Icon(Icons.fingerprint_outlined)), leading: const CircleAvatar(child: Icon(Icons.fingerprint_outlined)),
title: Text(l10n.l_add_fingerprint), title: Text(l10n.s_add_fingerprint),
subtitle: state.unlocked subtitle: state.unlocked
? Text(l10n.l_fingerprints_used(fingerprints)) ? Text(l10n.l_fingerprints_used(fingerprints))
: Text(state.hasPin : Text(state.hasPin
@ -54,13 +54,13 @@ Widget fidoBuildActions(
: null, : null,
), ),
], ],
ListTitle(l10n.w_manage, ListTitle(l10n.s_manage,
textStyle: Theme.of(context).textTheme.bodyLarge), textStyle: Theme.of(context).textTheme.bodyLarge),
ListTile( ListTile(
leading: const CircleAvatar(child: Icon(Icons.pin_outlined)), leading: const CircleAvatar(child: Icon(Icons.pin_outlined)),
title: Text(state.hasPin ? l10n.l_change_pin : l10n.l_set_pin), title: Text(state.hasPin ? l10n.s_change_pin : l10n.s_set_pin),
subtitle: Text(state.hasPin subtitle: Text(state.hasPin
? l10n.l_fido_pin_protection ? l10n.s_fido_pin_protection
: l10n.l_fido_pin_protection_optional), : l10n.l_fido_pin_protection_optional),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -75,7 +75,7 @@ Widget fidoBuildActions(
backgroundColor: theme.error, backgroundColor: theme.error,
child: const Icon(Icons.delete_outline), child: const Icon(Icons.delete_outline),
), ),
title: Text(l10n.l_reset_fido), title: Text(l10n.s_reset_fido),
subtitle: Text(l10n.l_factory_reset_this_app), subtitle: Text(l10n.l_factory_reset_this_app),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -38,15 +38,15 @@ class FidoLockedPage extends ConsumerWidget {
if (!state.hasPin) { if (!state.hasPin) {
if (state.bioEnroll != null) { if (state.bioEnroll != null) {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: noFingerprints, graphic: noFingerprints,
header: l10n.l_no_fingerprints, header: l10n.s_no_fingerprints,
message: l10n.l_set_pin_fingerprints, message: l10n.l_set_pin_fingerprints,
keyActionsBuilder: _buildActions, keyActionsBuilder: _buildActions,
); );
} else { } else {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: manageAccounts, graphic: manageAccounts,
header: state.credMgmt header: state.credMgmt
? l10n.l_no_discoverable_accounts ? l10n.l_no_discoverable_accounts
@ -59,7 +59,7 @@ class FidoLockedPage extends ConsumerWidget {
if (!state.credMgmt && state.bioEnroll == null) { if (!state.credMgmt && state.bioEnroll == null) {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: manageAccounts, graphic: manageAccounts,
header: l10n.l_ready_to_use, header: l10n.l_ready_to_use,
message: l10n.l_register_sk_on_websites, message: l10n.l_register_sk_on_websites,
@ -68,7 +68,7 @@ class FidoLockedPage extends ConsumerWidget {
} }
return AppPage( return AppPage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
keyActionsBuilder: _buildActions, keyActionsBuilder: _buildActions,
child: Column( child: Column(
children: [ children: [
@ -148,7 +148,7 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> {
controller: _pinController, controller: _pinController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.w_pin, labelText: l10n.s_pin,
helperText: '', // Prevents dialog resizing helperText: '', // Prevents dialog resizing
errorText: _pinIsWrong ? _getErrorText() : null, errorText: _pinIsWrong ? _getErrorText() : null,
errorMaxLines: 3, errorMaxLines: 3,
@ -187,7 +187,7 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> {
minLeadingWidth: 0, minLeadingWidth: 0,
trailing: ElevatedButton.icon( trailing: ElevatedButton.icon(
icon: const Icon(Icons.lock_open), icon: const Icon(Icons.lock_open),
label: Text(l10n.w_unlock), label: Text(l10n.s_unlock),
onPressed: onPressed:
_pinController.text.isNotEmpty && !_blocked ? _submit : null, _pinController.text.isNotEmpty && !_blocked ? _submit : null,
), ),

View File

@ -57,11 +57,11 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
final minPinLength = widget.state.minPinLength; final minPinLength = widget.state.minPinLength;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(hasPin ? l10n.l_change_pin : l10n.l_set_pin), title: Text(hasPin ? l10n.s_change_pin : l10n.s_set_pin),
actions: [ actions: [
TextButton( TextButton(
onPressed: isValid ? _submit : null, onPressed: isValid ? _submit : null,
child: Text(l10n.w_save), child: Text(l10n.s_save),
), ),
], ],
child: Padding( child: Padding(
@ -77,7 +77,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_current_pin, labelText: l10n.s_current_pin,
errorText: _currentIsWrong ? _currentPinError : null, errorText: _currentIsWrong ? _currentPinError : null,
errorMaxLines: 3, errorMaxLines: 3,
prefixIcon: const Icon(Icons.pin_outlined), prefixIcon: const Icon(Icons.pin_outlined),
@ -98,7 +98,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_new_pin, labelText: l10n.s_new_pin,
enabled: !hasPin || _currentPin.isNotEmpty, enabled: !hasPin || _currentPin.isNotEmpty,
errorText: _newIsWrong ? _newPinError : null, errorText: _newIsWrong ? _newPinError : null,
errorMaxLines: 3, errorMaxLines: 3,
@ -116,7 +116,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_confirm_pin, labelText: l10n.s_confirm_pin,
prefixIcon: const Icon(Icons.pin_outlined), prefixIcon: const Icon(Icons.pin_outlined),
enabled: enabled:
(!hasPin || _currentPin.isNotEmpty) && _newPin.isNotEmpty, (!hasPin || _currentPin.isNotEmpty) && _newPin.isNotEmpty,
@ -160,7 +160,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
.setPin(_newPin, oldPin: oldPin); .setPin(_newPin, oldPin: oldPin);
result.when(success: () { result.when(success: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
showMessage(context, l10n.l_pin_set); showMessage(context, l10n.s_pin_set);
}, failed: (retries, authBlocked) { }, failed: (retries, authBlocked) {
setState(() { setState(() {
if (authBlocked) { if (authBlocked) {

View File

@ -54,7 +54,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
.renameFingerprint(widget.fingerprint, _label); .renameFingerprint(widget.fingerprint, _label);
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(renamed); Navigator.of(context).pop(renamed);
showMessage(context, l10n.l_fingerprint_renamed); showMessage(context, l10n.s_fingerprint_renamed);
} catch (e) { } catch (e) {
final String errorMessage; final String errorMessage;
// TODO: Make this cleaner than importing desktop specific RpcError. // TODO: Make this cleaner than importing desktop specific RpcError.
@ -75,11 +75,11 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_rename_fp), title: Text(l10n.s_rename_fp),
actions: [ actions: [
TextButton( TextButton(
onPressed: _label.isNotEmpty ? _submit : null, onPressed: _label.isNotEmpty ? _submit : null,
child: Text(l10n.w_save), child: Text(l10n.s_save),
), ),
], ],
child: Padding( child: Padding(
@ -96,7 +96,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
buildCounter: buildByteCounterFor(_label), buildCounter: buildByteCounterFor(_label),
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.w_label, labelText: l10n.s_label,
prefixIcon: const Icon(Icons.fingerprint_outlined), prefixIcon: const Icon(Icons.fingerprint_outlined),
), ),
onChanged: (value) { onChanged: (value) {

View File

@ -63,7 +63,7 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_factory_reset), title: Text(l10n.s_factory_reset),
onCancel: () { onCancel: () {
_subscription?.cancel(); _subscription?.cancel();
}, },
@ -100,7 +100,7 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
}); });
} }
: null, : null,
child: Text(l10n.w_reset), child: Text(l10n.s_reset),
), ),
], ],
child: Padding( child: Padding(

View File

@ -48,7 +48,7 @@ class FidoUnlockedPage extends ConsumerWidget {
} }
final creds = data.value; final creds = data.value;
if (creds.isNotEmpty) { if (creds.isNotEmpty) {
children.add(ListTitle(l10n.w_credentials)); children.add(ListTitle(l10n.s_credentials));
children.addAll( children.addAll(
creds.map( creds.map(
(cred) => ListTile( (cred) => ListTile(
@ -96,7 +96,7 @@ class FidoUnlockedPage extends ConsumerWidget {
final fingerprints = data.value; final fingerprints = data.value;
if (fingerprints.isNotEmpty) { if (fingerprints.isNotEmpty) {
nFingerprints = fingerprints.length; nFingerprints = fingerprints.length;
children.add(ListTitle(l10n.w_fingerprints)); children.add(ListTitle(l10n.s_fingerprints));
children.addAll(fingerprints.map((fp) => ListTile( children.addAll(fingerprints.map((fp) => ListTile(
leading: CircleAvatar( leading: CircleAvatar(
foregroundColor: Theme.of(context).colorScheme.onSecondary, foregroundColor: Theme.of(context).colorScheme.onSecondary,
@ -137,7 +137,7 @@ class FidoUnlockedPage extends ConsumerWidget {
if (children.isNotEmpty) { if (children.isNotEmpty) {
return AppPage( return AppPage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
keyActionsBuilder: (context) => keyActionsBuilder: (context) =>
fidoBuildActions(context, node, state, nFingerprints), fidoBuildActions(context, node, state, nFingerprints),
child: Column( child: Column(
@ -147,9 +147,9 @@ class FidoUnlockedPage extends ConsumerWidget {
if (state.bioEnroll != null) { if (state.bioEnroll != null) {
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: noFingerprints, graphic: noFingerprints,
header: l10n.l_no_fingerprints, header: l10n.s_no_fingerprints,
message: l10n.l_add_one_or_more_fps, message: l10n.l_add_one_or_more_fps,
keyActionsBuilder: (context) => keyActionsBuilder: (context) =>
fidoBuildActions(context, node, state, 0), fidoBuildActions(context, node, state, 0),
@ -157,7 +157,7 @@ class FidoUnlockedPage extends ConsumerWidget {
} }
return MessagePage( return MessagePage(
title: Text(l10n.w_webauthn), title: Text(l10n.s_webauthn),
graphic: manageAccounts, graphic: manageAccounts,
header: l10n.l_no_discoverable_accounts, header: l10n.l_no_discoverable_accounts,
message: l10n.l_register_sk_on_websites, message: l10n.l_register_sk_on_websites,
@ -166,7 +166,7 @@ class FidoUnlockedPage extends ConsumerWidget {
} }
Widget _buildLoadingPage(BuildContext context) => AppPage( Widget _buildLoadingPage(BuildContext context) => AppPage(
title: Text(AppLocalizations.of(context)!.w_webauthn), title: Text(AppLocalizations.of(context)!.s_webauthn),
centered: true, centered: true,
delayedContent: true, delayedContent: true,
child: const CircularProgressIndicator(), child: const CircularProgressIndicator(),

View File

@ -4,31 +4,37 @@
"@_readme": { "@_readme": {
"notes": [ "notes": [
"All strings start with a Captial letter.", "All strings start with a Captial letter.",
"Group strings by category, but don't needlessly tie them to a section of the app if they can be re-used between several." "Group strings by category, but don't needlessly tie them to a section of the app if they can be re-used between several.",
"Run check_strings.py on the .arb file to detect problems, tweak @_lint_rules as needed per language."
], ],
"prefixes": { "prefixes": {
"w_": "A single word", "s_": "A single, or few words. Should be short enough to display on a button, or a header.",
"l_": "A single line. Should not be more than one sentence, and not end with a period.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.",
"p_": "One or more full sentences, with proper punctuation.", "p_": "One or more full sentences, with proper punctuation.",
"q_": "A question, ending in question mark." "q_": "A question, ending in question mark."
} }
}, },
"@_lint_rules": {
"s_max_words": 4,
"s_max_length": 32
},
"app_name": "Yubico Authenticator", "app_name": "Yubico Authenticator",
"w_save": "Save", "s_save": "Save",
"w_cancel": "Cancel", "s_cancel": "Cancel",
"w_close": "Close", "s_close": "Close",
"w_delete": "Delete", "s_delete": "Delete",
"w_quit": "Quit", "s_quit": "Quit",
"w_unlock": "Unlock", "s_unlock": "Unlock",
"w_calculate": "Calculate", "s_calculate": "Calculate",
"w_label": "Label", "s_label": "Label",
"w_name": "Name", "s_name": "Name",
"w_usb": "USB", "s_usb": "USB",
"w_nfc": "NFC", "s_nfc": "NFC",
"l_show_window": "Show window", "s_show_window": "Show window",
"l_hide_window": "Hide window", "s_hide_window": "Hide window",
"q_rename_target": "Rename {label}?", "q_rename_target": "Rename {label}?",
"@q_rename_target" : { "@q_rename_target" : {
"placeholders": { "placeholders": {
@ -36,62 +42,62 @@
} }
}, },
"w_about": "About", "s_about": "About",
"w_appearance": "Appearance", "s_appearance": "Appearance",
"w_authenticator": "Authenticator", "s_authenticator": "Authenticator",
"w_manage": "Manage", "s_manage": "Manage",
"w_setup": "Setup", "s_setup": "Setup",
"w_settings": "Settings", "s_settings": "Settings",
"w_webauthn": "WebAuthn", "s_webauthn": "WebAuthn",
"l_help_and_about": "Help and about", "s_help_and_about": "Help and about",
"l_help_and_feedback": "Help and feedback", "s_help_and_feedback": "Help and feedback",
"l_send_feedback": "Send us feedback", "s_send_feedback": "Send us feedback",
"l_i_need_help": "I need help", "s_i_need_help": "I need help",
"w_troubleshooting": "Troubleshooting", "s_troubleshooting": "Troubleshooting",
"l_terms_of_use": "Terms of use", "s_terms_of_use": "Terms of use",
"l_privacy_policy": "Privacy policy", "s_privacy_policy": "Privacy policy",
"l_open_src_licenses": "Open source licenses", "s_open_src_licenses": "Open source licenses",
"l_configure_yk": "Configure YubiKey", "s_configure_yk": "Configure YubiKey",
"l_please_wait": "Please wait\u2026", "s_please_wait": "Please wait\u2026",
"l_secret_key": "Secret key", "s_secret_key": "Secret key",
"l_invalid_length": "Invalid length", "s_invalid_length": "Invalid length",
"l_require_touch": "Require touch", "s_require_touch": "Require touch",
"q_have_account_info": "Have account info?", "q_have_account_info": "Have account info?",
"l_run_diagnostics": "Run diagnostics", "s_run_diagnostics": "Run diagnostics",
"l_log_level": "Log level: {level}", "s_log_level": "Log level: {level}",
"@l_log_level": { "@s_log_level": {
"placeholders": { "placeholders": {
"level": {} "level": {}
} }
}, },
"l_character_count": "Character count", "s_character_count": "Character count",
"l_learn_more": "Learn\u00a0more", "s_learn_more": "Learn\u00a0more",
"@_language": {}, "@_language": {},
"w_language": "Language", "s_language": "Language",
"l_enable_community_translations": "Enable community translations", "l_enable_community_translations": "Enable community translations",
"p_community_translations_desc": "These translations are provided and maintained by the community. They may contain errors or be incomplete.", "p_community_translations_desc": "These translations are provided and maintained by the community. They may contain errors or be incomplete.",
"@_theme": {}, "@_theme": {},
"l_app_theme": "App theme", "s_app_theme": "App theme",
"l_choose_app_theme": "Choose app theme", "s_choose_app_theme": "Choose app theme",
"l_system_default": "System default", "s_system_default": "System default",
"l_light_mode": "Light mode", "s_light_mode": "Light mode",
"l_dark_mode": "Dark mode", "s_dark_mode": "Dark mode",
"@_yubikey_selection": {}, "@_yubikey_selection": {},
"l_select_yk": "Select YubiKey", "s_select_yk": "Select YubiKey",
"l_select_to_scan": "Select to scan", "s_select_to_scan": "Select to scan",
"l_hide_device": "Hide device", "s_hide_device": "Hide device",
"l_show_hidden_devices": "Show hidden devices", "s_show_hidden_devices": "Show hidden devices",
"l_sn_serial": "S/N: {serial}", "s_sn_serial": "S/N: {serial}",
"@l_sn_serial" : { "@s_sn_serial" : {
"placeholders": { "placeholders": {
"serial": {} "serial": {}
} }
}, },
"l_fw_version": "F/W: {version}", "s_fw_version": "F/W: {version}",
"@l_fw_version" : { "@s_fw_version" : {
"placeholders": { "placeholders": {
"version": {} "version": {}
} }
@ -105,18 +111,18 @@
"l_place_on_nfc_reader": "Place your YubiKey on the NFC reader", "l_place_on_nfc_reader": "Place your YubiKey on the NFC reader",
"l_replace_yk_on_reader": "Place your YubiKey back on the reader", "l_replace_yk_on_reader": "Place your YubiKey back on the reader",
"l_remove_yk_from_reader": "Remove your YubiKey from the NFC reader", "l_remove_yk_from_reader": "Remove your YubiKey from the NFC reader",
"l_try_reinsert_yk": "Try to remove and reinsert your YubiKey.", "p_try_reinsert_yk": "Try to remove and reinsert your YubiKey.",
"l_touch_required": "Touch required", "s_touch_required": "Touch required",
"l_touch_button_now": "Touch the button on your YubiKey now", "l_touch_button_now": "Touch the button on your YubiKey now",
"l_keep_touching_yk": "Keep touching your YubiKey repeatedly\u2026", "l_keep_touching_yk": "Keep touching your YubiKey repeatedly\u2026",
"@_app_configuration": {}, "@_app_configuration": {},
"l_toggle_applications": "Toggle applications", "s_toggle_applications": "Toggle applications",
"l_min_one_interface": "At least one interface must be enabled", "l_min_one_interface": "At least one interface must be enabled",
"l_reconfiguring_yk": "Reconfiguring YubiKey\u2026", "s_reconfiguring_yk": "Reconfiguring YubiKey\u2026",
"l_config_updated": "Configuration updated", "s_config_updated": "Configuration updated",
"l_config_updated_reinsert": "Configuration updated, remove and reinsert your YubiKey", "l_config_updated_reinsert": "Configuration updated, remove and reinsert your YubiKey",
"l_app_not_supported": "Application not supported", "s_app_not_supported": "Application not supported",
"l_app_not_supported_on_yk": "The used YubiKey does not support '${app}' application", "l_app_not_supported_on_yk": "The used YubiKey does not support '${app}' application",
"@l_app_not_supported_on_yk" : { "@l_app_not_supported_on_yk" : {
"placeholders": { "placeholders": {
@ -124,65 +130,65 @@
} }
}, },
"l_app_not_supported_desc": "This application is not supported", "l_app_not_supported_desc": "This application is not supported",
"l_app_disabled": "Application disabled", "s_app_disabled": "Application disabled",
"l_app_disabled_desc": "Enable the '{app}' application on your YubiKey to access", "l_app_disabled_desc": "Enable the '{app}' application on your YubiKey to access",
"@l_app_disabled_desc" : { "@l_app_disabled_desc" : {
"placeholders": { "placeholders": {
"app": {} "app": {}
} }
}, },
"l_fido_disabled": "FIDO2 disabled", "s_fido_disabled": "FIDO2 disabled",
"l_webauthn_req_fido2": "WebAuthn requires the FIDO2 application to be enabled on your YubiKey", "l_webauthn_req_fido2": "WebAuthn requires the FIDO2 application to be enabled on your YubiKey",
"@_connectivity_issues": {}, "@_connectivity_issues": {},
"l_helper_not_responding": "The Helper process isn't responding", "l_helper_not_responding": "The Helper process isn't responding",
"l_yk_no_access": "This YubiKey cannot be accessed", "l_yk_no_access": "This YubiKey cannot be accessed",
"l_yk_inaccessible": "Device inaccessible", "s_yk_inaccessible": "Device inaccessible",
"l_open_connection_failed": "Failed to open connection", "l_open_connection_failed": "Failed to open connection",
"l_ccid_connection_failed": "Failed to open smart card connection", "l_ccid_connection_failed": "Failed to open smart card connection",
"p_ccid_service_unavailable": "Make sure your smart card service is functioning.", "p_ccid_service_unavailable": "Make sure your smart card service is functioning.",
"p_pcscd_unavailable": "Make sure pcscd is installed and running.", "p_pcscd_unavailable": "Make sure pcscd is installed and running.",
"l_no_yk_present": "No YubiKey present", "l_no_yk_present": "No YubiKey present",
"l_unknown_type": "Unknown type", "s_unknown_type": "Unknown type",
"l_unknown_device": "Unrecognized device", "s_unknown_device": "Unrecognized device",
"l_unsupported_yk": "Unsupported YubiKey", "s_unsupported_yk": "Unsupported YubiKey",
"l_yk_not_recognized": "Device not recognized", "s_yk_not_recognized": "Device not recognized",
"@_general_errors": {}, "@_general_errors": {},
"l_error_occured": "An error has occured", "l_error_occured": "An error has occured",
"l_application_error": "Application error", "s_application_error": "Application error",
"l_import_error": "Import error", "l_import_error": "Import error",
"l_file_not_found": "File not found", "l_file_not_found": "File not found",
"l_file_too_big": "File size too big", "l_file_too_big": "File size too big",
"l_filesystem_error": "File system operation error", "l_filesystem_error": "File system operation error",
"@_pins": {}, "@_pins": {},
"w_pin": "PIN", "s_pin": "PIN",
"l_set_pin": "Set PIN", "s_set_pin": "Set PIN",
"l_change_pin": "Change PIN", "s_change_pin": "Change PIN",
"l_current_pin": "Current PIN", "s_current_pin": "Current PIN",
"l_new_pin": "New PIN", "s_new_pin": "New PIN",
"l_confirm_pin": "Confirm PIN", "s_confirm_pin": "Confirm PIN",
"l_new_pin_len": "New PIN must be at least {length} characters", "l_new_pin_len": "New PIN must be at least {length} characters",
"@l_new_pin_len" : { "@l_new_pin_len" : {
"placeholders": { "placeholders": {
"length": {} "length": {}
} }
}, },
"l_pin_set": "PIN set", "s_pin_set": "PIN set",
"l_set_pin_failed": "Failed to set PIN: {message}", "l_set_pin_failed": "Failed to set PIN: {message}",
"@l_set_pin_failed" : { "@l_set_pin_failed" : {
"placeholders": { "placeholders": {
"message": {} "message": {}
} }
}, },
"l_wrong_pin_attempts_remaining": "Wrong PIN. {retries} attempt(s) remaining.", "l_wrong_pin_attempts_remaining": "Wrong PIN, {retries} attempt(s) remaining",
"@l_wrong_pin_attempts_remaining" : { "@l_wrong_pin_attempts_remaining" : {
"placeholders": { "placeholders": {
"retries": {} "retries": {}
} }
}, },
"l_fido_pin_protection": "FIDO PIN protection", "s_fido_pin_protection": "FIDO PIN protection",
"l_fido_pin_protection_optional": "Optional FIDO PIN protection", "l_fido_pin_protection_optional": "Optional FIDO PIN protection",
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey", "l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
"l_optionally_set_a_pin": "Optionally set a PIN to protect access to your YubiKey\nRegister as a Security Key on websites", "l_optionally_set_a_pin": "Optionally set a PIN to protect access to your YubiKey\nRegister as a Security Key on websites",
@ -199,20 +205,20 @@
}, },
"@_passwords": {}, "@_passwords": {},
"w_password": "Password", "s_password": "Password",
"l_manage_password": "Manage password", "s_manage_password": "Manage password",
"l_set_password": "Set password", "s_set_password": "Set password",
"l_password_set": "Password set", "s_password_set": "Password set",
"l_optional_password_protection": "Optional password protection", "l_optional_password_protection": "Optional password protection",
"l_new_password": "New password", "s_new_password": "New password",
"l_current_password": "Current password", "s_current_password": "Current password",
"l_confirm_password": "Confirm password", "s_confirm_password": "Confirm password",
"l_wrong_password": "Wrong password", "s_wrong_password": "Wrong password",
"l_remove_password": "Remove password", "s_remove_password": "Remove password",
"l_password_removed": "Password removed", "s_password_removed": "Password removed",
"l_remember_password": "Remember password", "s_remember_password": "Remember password",
"l_clear_saved_password": "Clear saved password", "s_clear_saved_password": "Clear saved password",
"l_password_forgotten": "Password forgotten", "s_password_forgotten": "Password forgotten",
"l_keystore_unavailable": "OS Keystore unavailable", "l_keystore_unavailable": "OS Keystore unavailable",
"l_remember_pw_failed": "Failed to remember password", "l_remember_pw_failed": "Failed to remember password",
"l_unlock_first": "Unlock with password first", "l_unlock_first": "Unlock with password first",
@ -227,10 +233,10 @@
"label": {} "label": {}
} }
}, },
"w_accounts": "Accounts", "s_accounts": "Accounts",
"l_no_accounts": "No accounts", "s_no_accounts": "No accounts",
"l_add_account": "Add account", "s_add_account": "Add account",
"l_account_added": "Account added", "s_account_added": "Account added",
"l_account_add_failed": "Failed adding account: {message}", "l_account_add_failed": "Failed adding account: {message}",
"@l_account_add_failed" : { "@l_account_add_failed" : {
"placeholders": { "placeholders": {
@ -240,19 +246,19 @@
"l_account_name_required": "Your account must have a name", "l_account_name_required": "Your account must have a name",
"l_name_already_exists": "This name already exists for the issuer", "l_name_already_exists": "This name already exists for the issuer",
"l_invalid_character_issuer": "Invalid character: ':' is not allowed in issuer", "l_invalid_character_issuer": "Invalid character: ':' is not allowed in issuer",
"w_pinned": "Pinned", "s_pinned": "Pinned",
"l_pin_account": "Pin account", "s_pin_account": "Pin account",
"l_unpin_account": "Unpin account", "s_unpin_account": "Unpin account",
"l_no_pinned_accounts": "No pinned accounts", "s_no_pinned_accounts": "No pinned accounts",
"l_rename_account": "Rename account", "s_rename_account": "Rename account",
"l_account_renamed": "Account renamed", "s_account_renamed": "Account renamed",
"p_rename_will_change_account_displayed": "This will change how the account is displayed in the list.", "p_rename_will_change_account_displayed": "This will change how the account is displayed in the list.",
"l_delete_account": "Delete account", "s_delete_account": "Delete account",
"l_account_deleted": "Account deleted", "s_account_deleted": "Account deleted",
"p_warning_delete_account": "Warning! This action will delete the account from your YubiKey.", "p_warning_delete_account": "Warning! This action will delete the account from your YubiKey.",
"p_warning_disable_credential": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.", "p_warning_disable_credential": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.",
"l_account_name": "Account name", "s_account_name": "Account name",
"l_search_accounts": "Search accounts", "s_search_accounts": "Search accounts",
"l_accounts_used": "{used} of {capacity} accounts used", "l_accounts_used": "{used} of {capacity} accounts used",
"@l_accounts_used" : { "@l_accounts_used" : {
"placeholders": { "placeholders": {
@ -260,19 +266,19 @@
"capacity": {} "capacity": {}
} }
}, },
"l_num_digits": "{num} digits", "s_num_digits": "{num} digits",
"@l_num_digits" : { "@s_num_digits" : {
"placeholders": { "placeholders": {
"num": {} "num": {}
} }
}, },
"l_num_sec": "{num} sec", "s_num_sec": "{num} sec",
"@l_num_sec" : { "@s_num_sec" : {
"placeholders": { "placeholders": {
"num": {} "num": {}
} }
}, },
"l_issuer_optional": "Issuer (optional)", "s_issuer_optional": "Issuer (optional)",
"@_fido_credentials": {}, "@_fido_credentials": {},
"l_credential": "Credential: {label}", "l_credential": "Credential: {label}",
@ -281,12 +287,12 @@
"label": {} "label": {}
} }
}, },
"w_credentials": "Credentials", "s_credentials": "Credentials",
"l_ready_to_use": "Ready to use", "l_ready_to_use": "Ready to use",
"l_register_sk_on_websites": "Register as a Security Key on websites", "l_register_sk_on_websites": "Register as a Security Key on websites",
"l_no_discoverable_accounts": "No discoverable accounts", "l_no_discoverable_accounts": "No discoverable accounts",
"l_delete_credential": "Delete credential", "s_delete_credential": "Delete credential",
"l_credential_deleted": "Credential deleted", "s_credential_deleted": "Credential deleted",
"p_warning_delete_credential": "This will delete the credential from your YubiKey.", "p_warning_delete_credential": "This will delete the credential from your YubiKey.",
"@_fingerprints": {}, "@_fingerprints": {},
@ -296,32 +302,32 @@
"label": {} "label": {}
} }
}, },
"w_fingerprints": "Fingerprints", "s_fingerprints": "Fingerprints",
"l_fingerprint_captured": "Fingerprint captured successfully!", "l_fingerprint_captured": "Fingerprint captured successfully!",
"l_fingerprint_added": "Fingerprint added", "s_fingerprint_added": "Fingerprint added",
"l_setting_name_failed": "Error setting name: {message}", "l_setting_name_failed": "Error setting name: {message}",
"@l_setting_name_failed" : { "@l_setting_name_failed" : {
"placeholders": { "placeholders": {
"message": {} "message": {}
} }
}, },
"l_add_fingerprint": "Add fingerprint", "s_add_fingerprint": "Add fingerprint",
"l_fp_step_1_capture": "Step 1/2: Capture fingerprint", "l_fp_step_1_capture": "Step 1/2: Capture fingerprint",
"l_fp_step_2_name": "Step 2/2: Name fingerprint", "l_fp_step_2_name": "Step 2/2: Name fingerprint",
"l_delete_fingerprint": "Delete fingerprint", "s_delete_fingerprint": "Delete fingerprint",
"l_fingerprint_deleted": "Fingerprint deleted", "s_fingerprint_deleted": "Fingerprint deleted",
"p_warning_delete_fingerprint": "This will delete the fingerprint from your YubiKey.", "p_warning_delete_fingerprint": "This will delete the fingerprint from your YubiKey.",
"l_no_fingerprints": "No fingerprints", "s_no_fingerprints": "No fingerprints",
"l_set_pin_fingerprints": "Set a PIN to register fingerprints", "l_set_pin_fingerprints": "Set a PIN to register fingerprints",
"l_no_fps_added": "No fingerprints have been added", "l_no_fps_added": "No fingerprints have been added",
"l_fingerprint_renamed": "Fingerprint renamed", "s_rename_fp": "Rename fingerprint",
"s_fingerprint_renamed": "Fingerprint renamed",
"l_rename_fp_failed": "Error renaming: {message}", "l_rename_fp_failed": "Error renaming: {message}",
"@l_rename_fp_failed" : { "@l_rename_fp_failed" : {
"placeholders": { "placeholders": {
"message": {} "message": {}
} }
}, },
"l_rename_fp": "Rename fingerprint",
"l_add_one_or_more_fps": "Add one or more (up to five) fingerprints", "l_add_one_or_more_fps": "Add one or more (up to five) fingerprints",
"l_fingerprints_used": "{used}/5 fingerprints registered", "l_fingerprints_used": "{used}/5 fingerprints registered",
"@l_fingerprints_used": { "@l_fingerprints_used": {
@ -333,16 +339,16 @@
"p_will_change_label_fp": "This will change the label of the fingerprint.", "p_will_change_label_fp": "This will change the label of the fingerprint.",
"@_permissions": {}, "@_permissions": {},
"l_enable_nfc": "Enable NFC", "s_enable_nfc": "Enable NFC",
"l_permission_denied": "Permission denied", "s_permission_denied": "Permission denied",
"l_elevating_permissions": "Elevating permissions\u2026", "l_elevating_permissions": "Elevating permissions\u2026",
"l_review_permissions": "Review permissions", "s_review_permissions": "Review permissions",
"p_elevated_permissions_required": "Managing this device requires elevated privileges.", "p_elevated_permissions_required": "Managing this device requires elevated privileges.",
"p_webauthn_elevated_permissions_required": "WebAuthn management requires elevated privileges.", "p_webauthn_elevated_permissions_required": "WebAuthn management requires elevated privileges.",
"p_need_camera_permission": "Yubico Authenticator needs Camera permissions for scanning QR codes.", "p_need_camera_permission": "Yubico Authenticator needs Camera permissions for scanning QR codes.",
"@_qr_codes": {}, "@_qr_codes": {},
"l_qr_scan": "Scan QR code", "s_qr_scan": "Scan QR code",
"l_qr_scanned": "Scanned QR code", "l_qr_scanned": "Scanned QR code",
"l_invalid_qr": "Invalid QR code", "l_invalid_qr": "Invalid QR code",
"l_qr_not_found": "No QR code found", "l_qr_not_found": "No QR code found",
@ -355,15 +361,15 @@
"l_point_camera_scan": "Point your camera at a QR code to scan it", "l_point_camera_scan": "Point your camera at a QR code to scan it",
"q_want_to_scan": "Would like to scan?", "q_want_to_scan": "Would like to scan?",
"q_no_qr": "No QR code?", "q_no_qr": "No QR code?",
"l_enter_manually": "Enter manually", "s_enter_manually": "Enter manually",
"@_factory_reset": {}, "@_factory_reset": {},
"w_reset": "Reset", "s_reset": "Reset",
"l_factory_reset": "Factory reset", "s_factory_reset": "Factory reset",
"l_factory_reset_this_app": "Factory reset this application", "l_factory_reset_this_app": "Factory reset this application",
"l_reset_oath": "Reset OATH", "s_reset_oath": "Reset OATH",
"l_oath_application_reset": "OATH application reset", "l_oath_application_reset": "OATH application reset",
"l_reset_fido": "Reset FIDO", "s_reset_fido": "Reset FIDO",
"l_fido_app_reset": "FIDO application reset", "l_fido_app_reset": "FIDO application reset",
"l_press_reset_to_begin": "Press reset to begin\u2026", "l_press_reset_to_begin": "Press reset to begin\u2026",
"l_reset_failed": "Error performing reset: {message}", "l_reset_failed": "Error performing reset: {message}",
@ -379,9 +385,9 @@
"@_copy_to_clipboard": {}, "@_copy_to_clipboard": {},
"l_copy_to_clipboard": "Copy to clipboard", "l_copy_to_clipboard": "Copy to clipboard",
"l_code_copied": "Code copied", "s_code_copied": "Code copied",
"l_code_copied_clipboard": "Code copied to clipboard", "l_code_copied_clipboard": "Code copied to clipboard",
"l_copy_log": "Copy log", "s_copy_log": "Copy log",
"l_log_copied": "Log copied to clipboard", "l_log_copied": "Log copied to clipboard",
"l_diagnostics_copied": "Diagnostic data copied to clipboard", "l_diagnostics_copied": "Diagnostic data copied to clipboard",
"p_target_copied_clipboard": "{label} copied to clipboard.", "p_target_copied_clipboard": "{label} copied to clipboard.",
@ -392,16 +398,16 @@
}, },
"@_custom_icons": {}, "@_custom_icons": {},
"l_custom_icons": "Custom icons", "s_custom_icons": "Custom icons",
"l_set_icons_for_accounts": "Set icons for accounts", "l_set_icons_for_accounts": "Set icons for accounts",
"p_custom_icons_description": "Icon packs can make your accounts more easily distinguishable with familiar logos and colors.", "p_custom_icons_description": "Icon packs can make your accounts more easily distinguishable with familiar logos and colors.",
"l_replace_icon_pack": "Replace icon pack", "s_replace_icon_pack": "Replace icon pack",
"l_loading_icon_pack": "Loading icon pack\u2026", "l_loading_icon_pack": "Loading icon pack\u2026",
"l_load_icon_pack": "Load icon pack", "s_load_icon_pack": "Load icon pack",
"l_remove_icon_pack": "Remove icon pack", "s_remove_icon_pack": "Remove icon pack",
"l_icon_pack_removed": "Icon pack removed", "l_icon_pack_removed": "Icon pack removed",
"l_remove_icon_pack_failed": "Error removing icon pack", "l_remove_icon_pack_failed": "Error removing icon pack",
"l_choose_icon_pack": "Choose icon pack", "s_choose_icon_pack": "Choose icon pack",
"l_icon_pack_imported": "Icon pack imported", "l_icon_pack_imported": "Icon pack imported",
"l_import_icon_pack_failed": "Error importing icon pack: {message}", "l_import_icon_pack_failed": "Error importing icon pack: {message}",
"@l_import_icon_pack_failed": { "@l_import_icon_pack_failed": {
@ -412,24 +418,24 @@
"l_invalid_icon_pack": "Invalid icon pack", "l_invalid_icon_pack": "Invalid icon pack",
"@_android_settings": {}, "@_android_settings": {},
"l_nfc_options": "NFC options", "s_nfc_options": "NFC options",
"l_on_yk_nfc_tap": "On YubiKey NFC tap", "l_on_yk_nfc_tap": "On YubiKey NFC tap",
"l_launch_ya": "Launch Yubico Authenticator", "l_launch_ya": "Launch Yubico Authenticator",
"l_copy_otp_clipboard": "Copy OTP to clipboard", "l_copy_otp_clipboard": "Copy OTP to clipboard",
"l_launch_and_copy_otp": "Launch app and copy OTP", "l_launch_and_copy_otp": "Launch app and copy OTP",
"l_kbd_layout_for_static": "Keyboard layout (for static password)", "l_kbd_layout_for_static": "Keyboard layout (for static password)",
"l_choose_kbd_layout": "Choose keyboard layout", "s_choose_kbd_layout": "Choose keyboard layout",
"l_bypass_touch_requirement": "Bypass touch requirement", "l_bypass_touch_requirement": "Bypass touch requirement",
"l_bypass_touch_requirement_on": "Accounts that require touch are automatically shown over NFC", "l_bypass_touch_requirement_on": "Accounts that require touch are automatically shown over NFC",
"l_bypass_touch_requirement_off": "Accounts that require touch need an additional tap over NFC", "l_bypass_touch_requirement_off": "Accounts that require touch need an additional tap over NFC",
"l_silence_nfc_sounds": "Silence NFC sounds", "s_silence_nfc_sounds": "Silence NFC sounds",
"l_silence_nfc_sounds_on": "No sounds will be played on NFC tap", "l_silence_nfc_sounds_on": "No sounds will be played on NFC tap",
"l_silence_nfc_sounds_off": "Sound will play on NFC tap", "l_silence_nfc_sounds_off": "Sound will play on NFC tap",
"l_usb_options": "USB options", "s_usb_options": "USB options",
"l_launch_app_on_usb": "Launch when YubiKey is connected", "l_launch_app_on_usb": "Launch when YubiKey is connected",
"l_launch_app_on_usb_on": "This prevents other apps from using the YubiKey over USB", "l_launch_app_on_usb_on": "This prevents other apps from using the YubiKey over USB",
"l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB",
"l_allow_screenshots": "Allow screenshots", "s_allow_screenshots": "Allow screenshots",
"@_eof": {} "@_eof": {}
} }

View File

@ -116,7 +116,7 @@ class _CapabilitiesForm extends StatelessWidget {
if (usbCapabilities != 0) ...[ if (usbCapabilities != 0) ...[
ListTile( ListTile(
leading: const Icon(Icons.usb), leading: const Icon(Icons.usb),
title: Text(l10n.w_usb), title: Text(l10n.s_usb),
contentPadding: const EdgeInsets.only(bottom: 8), contentPadding: const EdgeInsets.only(bottom: 8),
horizontalTitleGap: 0, horizontalTitleGap: 0,
), ),
@ -137,7 +137,7 @@ class _CapabilitiesForm extends StatelessWidget {
), ),
ListTile( ListTile(
leading: nfcIcon, leading: nfcIcon,
title: Text(l10n.w_nfc), title: Text(l10n.s_nfc),
contentPadding: const EdgeInsets.only(bottom: 8), contentPadding: const EdgeInsets.only(bottom: 8),
horizontalTitleGap: 0, horizontalTitleGap: 0,
), ),
@ -212,7 +212,7 @@ class _ManagementScreenState extends ConsumerState<ManagementScreen> {
// This will take longer, show a message // This will take longer, show a message
close = showMessage( close = showMessage(
context, context,
l10n.l_reconfiguring_yk, l10n.s_reconfiguring_yk,
duration: const Duration(seconds: 8), duration: const Duration(seconds: 8),
); );
} }
@ -225,7 +225,7 @@ class _ManagementScreenState extends ConsumerState<ManagementScreen> {
); );
if (!mounted) return; if (!mounted) return;
if (!reboot) Navigator.pop(context); if (!reboot) Navigator.pop(context);
showMessage(context, l10n.l_config_updated); showMessage(context, l10n.s_config_updated);
} finally { } finally {
close?.call(); close?.call();
} }
@ -250,7 +250,7 @@ class _ManagementScreenState extends ConsumerState<ManagementScreen> {
showMessage( showMessage(
context, context,
widget.deviceData.node.maybeMap( widget.deviceData.node.maybeMap(
nfcReader: (_) => l10n.l_config_updated, nfcReader: (_) => l10n.s_config_updated,
orElse: () => l10n.l_config_updated_reinsert)); orElse: () => l10n.l_config_updated_reinsert));
Navigator.pop(context); Navigator.pop(context);
} }
@ -318,12 +318,12 @@ class _ManagementScreenState extends ConsumerState<ManagementScreen> {
); );
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_toggle_applications), title: Text(l10n.s_toggle_applications),
actions: [ actions: [
TextButton( TextButton(
onPressed: canSave ? _submitForm : null, onPressed: canSave ? _submitForm : null,
key: management_keys.saveButtonKey, key: management_keys.saveButtonKey,
child: Text(l10n.w_save), child: Text(l10n.s_save),
), ),
], ],
child: child, child: child,

View File

@ -34,7 +34,7 @@ class IconPackDialog extends ConsumerWidget {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final iconPack = ref.watch(iconPackProvider); final iconPack = ref.watch(iconPackProvider);
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_custom_icons), title: Text(l10n.s_custom_icons),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0), padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column( child: Column(
@ -66,9 +66,9 @@ class IconPackDialog extends ConsumerWidget {
Widget? _action(AsyncValue<IconPack?> iconPack, AppLocalizations l10n) => Widget? _action(AsyncValue<IconPack?> iconPack, AppLocalizations l10n) =>
iconPack.when( iconPack.when(
data: (IconPack? data) => _ImportActionChip( data: (IconPack? data) => _ImportActionChip(
data != null ? l10n.l_replace_icon_pack : l10n.l_load_icon_pack), data != null ? l10n.s_replace_icon_pack : l10n.s_load_icon_pack),
error: (Object error, StackTrace stackTrace) => error: (Object error, StackTrace stackTrace) =>
_ImportActionChip(l10n.l_load_icon_pack), _ImportActionChip(l10n.s_load_icon_pack),
loading: () => _ImportActionChip( loading: () => _ImportActionChip(
l10n.l_loading_icon_pack, l10n.l_loading_icon_pack,
avatar: const CircularProgressIndicator(), avatar: const CircularProgressIndicator(),
@ -96,7 +96,7 @@ class _DialogDescription extends ConsumerWidget {
TextSpan _createLearnMoreLink(BuildContext context) { TextSpan _createLearnMoreLink(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
return TextSpan( return TextSpan(
text: AppLocalizations.of(context)!.l_learn_more, text: AppLocalizations.of(context)!.s_learn_more,
style: theme.textTheme.bodyMedium style: theme.textTheme.bodyMedium
?.copyWith(color: theme.colorScheme.primary), ?.copyWith(color: theme.colorScheme.primary),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
@ -134,7 +134,7 @@ class _IconPackDescription extends ConsumerWidget {
Row( Row(
children: [ children: [
IconButton( IconButton(
tooltip: l10n.l_remove_icon_pack, tooltip: l10n.s_remove_icon_pack,
onPressed: () async { onPressed: () async {
final removePackStatus = final removePackStatus =
await ref.read(iconPackProvider.notifier).removePack(); await ref.read(iconPackProvider.notifier).removePack();
@ -179,7 +179,7 @@ class _ImportActionChip extends ConsumerWidget {
type: FileType.custom, type: FileType.custom,
allowMultiple: false, allowMultiple: false,
lockParentWindow: true, lockParentWindow: true,
dialogTitle: l10n.l_choose_icon_pack); dialogTitle: l10n.s_choose_icon_pack);
if (result != null && result.files.isNotEmpty) { if (result != null && result.files.isNotEmpty) {
final importStatus = await ref final importStatus = await ref
.read(iconPackProvider.notifier) .read(iconPackProvider.notifier)

View File

@ -46,7 +46,7 @@ class AccountDialog extends ConsumerWidget {
final copy = final copy =
actions.firstWhere(((e) => e.text == l10n.l_copy_to_clipboard)); actions.firstWhere(((e) => e.text == l10n.l_copy_to_clipboard));
final delete = actions.firstWhere(((e) => e.text == l10n.l_delete_account)); final delete = actions.firstWhere(((e) => e.text == l10n.s_delete_account));
final colors = { final colors = {
copy: Pair(theme.primary, theme.onPrimary), copy: Pair(theme.primary, theme.onPrimary),
delete: Pair(theme.error, theme.onError), delete: Pair(theme.error, theme.onError),
@ -54,7 +54,7 @@ class AccountDialog extends ConsumerWidget {
// If we can't copy, but can calculate, highlight that button instead // If we can't copy, but can calculate, highlight that button instead
if (copy.intent == null) { if (copy.intent == null) {
final calculates = actions.where(((e) => e.text == l10n.w_calculate)); final calculates = actions.where(((e) => e.text == l10n.s_calculate));
if (calculates.isNotEmpty) { if (calculates.isNotEmpty) {
colors[calculates.first] = Pair(theme.primary, theme.onPrimary); colors[calculates.first] = Pair(theme.primary, theme.onPrimary);
} }

View File

@ -73,12 +73,12 @@ class AccountHelper {
), ),
if (manual) if (manual)
MenuAction( MenuAction(
text: l10n.w_calculate, text: l10n.s_calculate,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
intent: ready ? const CalculateIntent() : null, intent: ready ? const CalculateIntent() : null,
), ),
MenuAction( MenuAction(
text: pinned ? l10n.l_unpin_account : l10n.l_pin_account, text: pinned ? l10n.s_unpin_account : l10n.s_pin_account,
icon: pinned icon: pinned
? pushPinStrokeIcon ? pushPinStrokeIcon
: const Icon(Icons.push_pin_outlined), : const Icon(Icons.push_pin_outlined),
@ -87,11 +87,11 @@ class AccountHelper {
if (data.info.version.isAtLeast(5, 3)) if (data.info.version.isAtLeast(5, 3))
MenuAction( MenuAction(
icon: const Icon(Icons.edit_outlined), icon: const Icon(Icons.edit_outlined),
text: l10n.l_rename_account, text: l10n.s_rename_account,
intent: const EditIntent(), intent: const EditIntent(),
), ),
MenuAction( MenuAction(
text: l10n.l_delete_account, text: l10n.s_delete_account,
icon: const Icon(Icons.delete_outline), icon: const Icon(Icons.delete_outline),
intent: const DeleteIntent(), intent: const DeleteIntent(),
), ),

View File

@ -34,7 +34,7 @@ class AccountList extends ConsumerWidget {
final favorites = ref.watch(favoritesProvider); final favorites = ref.watch(favoritesProvider);
if (credentials.isEmpty) { if (credentials.isEmpty) {
return Center( return Center(
child: Text(l10n.l_no_accounts), child: Text(l10n.s_no_accounts),
); );
} }
@ -47,13 +47,13 @@ class AccountList extends ConsumerWidget {
policy: WidgetOrderTraversalPolicy(), policy: WidgetOrderTraversalPolicy(),
child: Column( child: Column(
children: [ children: [
if (pinnedCreds.isNotEmpty) ListTitle(l10n.w_pinned), if (pinnedCreds.isNotEmpty) ListTitle(l10n.s_pinned),
...pinnedCreds.map( ...pinnedCreds.map(
(entry) => AccountView( (entry) => AccountView(
entry.credential, entry.credential,
), ),
), ),
if (creds.isNotEmpty) ListTitle(l10n.w_accounts), if (creds.isNotEmpty) ListTitle(l10n.s_accounts),
...creds.map( ...creds.map(
(entry) => AccountView( (entry) => AccountView(
entry.credential, entry.credential,

View File

@ -188,7 +188,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
} }
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, l10n.l_account_added); showMessage(context, l10n.s_account_added);
} on CancellationException catch (_) { } on CancellationException catch (_) {
// ignored // ignored
} catch (e) { } catch (e) {
@ -244,7 +244,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
0) != 0) !=
0) { 0) {
if (oathState == null) { if (oathState == null) {
_promptController?.updateContent(title: l10n.l_please_wait); _promptController?.updateContent(title: l10n.s_please_wait);
} else if (oathState.locked) { } else if (oathState.locked) {
_promptController?.close(); _promptController?.close();
} else { } else {
@ -256,12 +256,12 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
)); ));
} }
} else { } else {
_promptController?.updateContent(title: l10n.l_unsupported_yk); _promptController?.updateContent(title: l10n.s_unsupported_yk);
} }
}, error: (error, _) { }, error: (error, _) {
_promptController?.updateContent(title: l10n.l_unsupported_yk); _promptController?.updateContent(title: l10n.s_unsupported_yk);
}, loading: () { }, loading: () {
_promptController?.updateContent(title: l10n.l_please_wait); _promptController?.updateContent(title: l10n.s_please_wait);
}); });
} }
@ -343,7 +343,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
_promptController = promptUserInteraction( _promptController = promptUserInteraction(
context, context,
title: l10n.l_insert_yk, title: l10n.l_insert_yk,
description: l10n.l_add_account, description: l10n.s_add_account,
icon: const Icon(Icons.usb), icon: const Icon(Icons.usb),
onCancel: () { onCancel: () {
_otpauthUri = null; _otpauthUri = null;
@ -358,11 +358,11 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
} }
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_add_account), title: Text(l10n.s_add_account),
actions: [ actions: [
TextButton( TextButton(
onPressed: isValid ? submit : null, onPressed: isValid ? submit : null,
child: Text(l10n.w_save, key: keys.saveButton), child: Text(l10n.s_save, key: keys.saveButton),
), ),
], ],
child: FileDropTarget( child: FileDropTarget(
@ -402,7 +402,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
buildCounter: buildByteCounterFor(issuerText), buildCounter: buildByteCounterFor(issuerText),
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_issuer_optional, labelText: l10n.s_issuer_optional,
helperText: helperText:
'', // Prevents dialog resizing when disabled '', // Prevents dialog resizing when disabled
prefixIcon: const Icon(Icons.business_outlined), prefixIcon: const Icon(Icons.business_outlined),
@ -431,7 +431,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.person_outline), prefixIcon: const Icon(Icons.person_outline),
labelText: l10n.l_account_name, labelText: l10n.s_account_name,
helperText: helperText:
'', // Prevents dialog resizing when disabled '', // Prevents dialog resizing when disabled
errorText: (byteLength(nameText) > nameMaxLength) errorText: (byteLength(nameText) > nameMaxLength)
@ -474,9 +474,9 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
), ),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.key_outlined), prefixIcon: const Icon(Icons.key_outlined),
labelText: l10n.l_secret_key, labelText: l10n.s_secret_key,
errorText: _validateSecretLength && !secretLengthValid errorText: _validateSecretLength && !secretLengthValid
? l10n.l_invalid_length ? l10n.s_invalid_length
: null), : null),
readOnly: _qrState == _QrScanState.success, readOnly: _qrState == _QrScanState.success,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
@ -502,7 +502,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
strokeWidth: 2.0), strokeWidth: 2.0),
label: _qrState == _QrScanState.success label: _qrState == _QrScanState.success
? Text(l10n.l_qr_scanned) ? Text(l10n.l_qr_scanned)
: Text(l10n.l_qr_scan), : Text(l10n.s_qr_scan),
onPressed: () { onPressed: () {
_scanQrCode(qrScanner); _scanQrCode(qrScanner);
}), }),
@ -515,7 +515,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
children: [ children: [
if (oathState?.version.isAtLeast(4, 2) ?? true) if (oathState?.version.isAtLeast(4, 2) ?? true)
FilterChip( FilterChip(
label: Text(l10n.l_require_touch), label: Text(l10n.s_require_touch),
selected: _touch, selected: _touch,
onSelected: (value) { onSelected: (value) {
setState(() { setState(() {
@ -557,7 +557,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
selected: int.tryParse(_periodController.text) != selected: int.tryParse(_periodController.text) !=
defaultPeriod, defaultPeriod,
itemBuilder: ((value) => itemBuilder: ((value) =>
Text(l10n.l_num_sec(value))), Text(l10n.s_num_sec(value))),
onChanged: _qrState != _QrScanState.success onChanged: _qrState != _QrScanState.success
? (period) { ? (period) {
setState(() { setState(() {
@ -571,7 +571,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
value: _digits, value: _digits,
selected: _digits != defaultDigits, selected: _digits != defaultDigits,
itemBuilder: (value) => itemBuilder: (value) =>
Text(l10n.l_num_digits(value)), Text(l10n.s_num_digits(value)),
onChanged: _qrState != _QrScanState.success onChanged: _qrState != _QrScanState.success
? (digits) { ? (digits) {
setState(() { setState(() {

View File

@ -37,7 +37,7 @@ class DeleteAccountDialog extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_delete_account), title: Text(l10n.s_delete_account),
actions: [ actions: [
TextButton( TextButton(
key: keys.deleteButton, key: keys.deleteButton,
@ -49,14 +49,14 @@ class DeleteAccountDialog extends ConsumerWidget {
await ref.read(withContextProvider)( await ref.read(withContextProvider)(
(context) async { (context) async {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
showMessage(context, l10n.l_account_deleted); showMessage(context, l10n.s_account_deleted);
}, },
); );
} on CancellationException catch (_) { } on CancellationException catch (_) {
// ignored // ignored
} }
}, },
child: Text(l10n.w_delete), child: Text(l10n.s_delete),
), ),
], ],
child: Padding( child: Padding(

View File

@ -45,9 +45,9 @@ Widget oathBuildActions(
final theme = Theme.of(context).colorScheme; final theme = Theme.of(context).colorScheme;
return SimpleDialog( return SimpleDialog(
children: [ children: [
ListTitle(l10n.w_setup, textStyle: Theme.of(context).textTheme.bodyLarge), ListTitle(l10n.s_setup, textStyle: Theme.of(context).textTheme.bodyLarge),
ListTile( ListTile(
title: Text(l10n.l_add_account), title: Text(l10n.s_add_account),
key: keys.addAccountAction, key: keys.addAccountAction,
leading: leading:
const CircleAvatar(child: Icon(Icons.person_add_alt_1_outlined)), const CircleAvatar(child: Icon(Icons.person_add_alt_1_outlined)),
@ -87,11 +87,11 @@ Widget oathBuildActions(
} }
: null, : null,
), ),
ListTitle(l10n.w_manage, ListTitle(l10n.s_manage,
textStyle: Theme.of(context).textTheme.bodyLarge), textStyle: Theme.of(context).textTheme.bodyLarge),
ListTile( ListTile(
key: keys.customIconsAction, key: keys.customIconsAction,
title: Text(l10n.l_custom_icons), title: Text(l10n.s_custom_icons),
subtitle: Text(l10n.l_set_icons_for_accounts), subtitle: Text(l10n.l_set_icons_for_accounts),
leading: const CircleAvatar( leading: const CircleAvatar(
child: Icon(Icons.image_outlined), child: Icon(Icons.image_outlined),
@ -108,7 +108,7 @@ Widget oathBuildActions(
ListTile( ListTile(
key: keys.setOrManagePasswordAction, key: keys.setOrManagePasswordAction,
title: Text( title: Text(
oathState.hasKey ? l10n.l_manage_password : l10n.l_set_password), oathState.hasKey ? l10n.s_manage_password : l10n.s_set_password),
subtitle: Text(l10n.l_optional_password_protection), subtitle: Text(l10n.l_optional_password_protection),
leading: const CircleAvatar(child: Icon(Icons.password_outlined)), leading: const CircleAvatar(child: Icon(Icons.password_outlined)),
onTap: () { onTap: () {
@ -120,7 +120,7 @@ Widget oathBuildActions(
}), }),
ListTile( ListTile(
key: keys.resetAction, key: keys.resetAction,
title: Text(l10n.l_reset_oath), title: Text(l10n.s_reset_oath),
subtitle: Text(l10n.l_factory_reset_this_app), subtitle: Text(l10n.l_factory_reset_this_app),
leading: CircleAvatar( leading: CircleAvatar(
foregroundColor: theme.onError, foregroundColor: theme.onError,

View File

@ -48,7 +48,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
if (result) { if (result) {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, AppLocalizations.of(context)!.l_password_set); showMessage(context, AppLocalizations.of(context)!.s_password_set);
} else { } else {
setState(() { setState(() {
_currentIsWrong = true; _currentIsWrong = true;
@ -64,12 +64,12 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
(!widget.state.hasKey || _currentPassword.isNotEmpty); (!widget.state.hasKey || _currentPassword.isNotEmpty);
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_manage_password), title: Text(l10n.s_manage_password),
actions: [ actions: [
TextButton( TextButton(
onPressed: isValid ? _submit : null, onPressed: isValid ? _submit : null,
key: keys.savePasswordButton, key: keys.savePasswordButton,
child: Text(l10n.w_save), child: Text(l10n.s_save),
) )
], ],
child: Padding( child: Padding(
@ -85,9 +85,9 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
key: keys.currentPasswordField, key: keys.currentPasswordField,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_current_password, labelText: l10n.s_current_password,
prefixIcon: const Icon(Icons.password_outlined), prefixIcon: const Icon(Icons.password_outlined),
errorText: _currentIsWrong ? l10n.l_wrong_password : null, errorText: _currentIsWrong ? l10n.s_wrong_password : null,
errorMaxLines: 3), errorMaxLines: 3),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
onChanged: (value) { onChanged: (value) {
@ -111,7 +111,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
if (result) { if (result) {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, l10n.l_password_removed); showMessage(context, l10n.s_password_removed);
} else { } else {
setState(() { setState(() {
_currentIsWrong = true; _currentIsWrong = true;
@ -119,18 +119,18 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
} }
} }
: null, : null,
child: Text(l10n.l_remove_password), child: Text(l10n.s_remove_password),
), ),
if (widget.state.remembered) if (widget.state.remembered)
OutlinedButton( OutlinedButton(
child: Text(l10n.l_clear_saved_password), child: Text(l10n.s_clear_saved_password),
onPressed: () async { onPressed: () async {
await ref await ref
.read(oathStateProvider(widget.path).notifier) .read(oathStateProvider(widget.path).notifier)
.forgetPassword(); .forgetPassword();
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, l10n.l_password_forgotten); showMessage(context, l10n.s_password_forgotten);
}, },
), ),
], ],
@ -143,7 +143,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_new_password, labelText: l10n.s_new_password,
prefixIcon: const Icon(Icons.password_outlined), prefixIcon: const Icon(Icons.password_outlined),
enabled: !widget.state.hasKey || _currentPassword.isNotEmpty, enabled: !widget.state.hasKey || _currentPassword.isNotEmpty,
), ),
@ -164,7 +164,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_confirm_password, labelText: l10n.s_confirm_password,
prefixIcon: const Icon(Icons.password_outlined), prefixIcon: const Icon(Icons.password_outlined),
enabled: enabled:
(!widget.state.hasKey || _currentPassword.isNotEmpty) && (!widget.state.hasKey || _currentPassword.isNotEmpty) &&

View File

@ -42,12 +42,12 @@ class OathScreen extends ConsumerWidget {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ref.watch(oathStateProvider(devicePath)).when( return ref.watch(oathStateProvider(devicePath)).when(
loading: () => MessagePage( loading: () => MessagePage(
title: Text(l10n.w_authenticator), title: Text(l10n.s_authenticator),
graphic: const CircularProgressIndicator(), graphic: const CircularProgressIndicator(),
delayedContent: true, delayedContent: true,
), ),
error: (error, _) => AppFailurePage( error: (error, _) => AppFailurePage(
title: Text(l10n.w_authenticator), title: Text(l10n.s_authenticator),
cause: error, cause: error,
), ),
data: (oathState) => oathState.locked data: (oathState) => oathState.locked
@ -66,7 +66,7 @@ class _LockedView extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return AppPage( return AppPage(
title: Text(AppLocalizations.of(context)!.w_authenticator), title: Text(AppLocalizations.of(context)!.s_authenticator),
keyActionsBuilder: (context) => keyActionsBuilder: (context) =>
oathBuildActions(context, devicePath, oathState, ref), oathBuildActions(context, devicePath, oathState, ref),
child: Padding( child: Padding(
@ -116,10 +116,10 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
.select((value) => value?.length)); .select((value) => value?.length));
if (numCreds == 0) { if (numCreds == 0) {
return MessagePage( return MessagePage(
title: Text(l10n.w_authenticator), title: Text(l10n.s_authenticator),
key: keys.noAccountsView, key: keys.noAccountsView,
graphic: noAccounts, graphic: noAccounts,
header: l10n.l_no_accounts, header: l10n.s_no_accounts,
keyActionsBuilder: (context) => oathBuildActions( keyActionsBuilder: (context) => oathBuildActions(
context, widget.devicePath, widget.oathState, ref, context, widget.devicePath, widget.oathState, ref,
used: 0), used: 0),
@ -154,7 +154,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
style: textTheme.titleMedium style: textTheme.titleMedium
?.copyWith(fontSize: textTheme.titleSmall?.fontSize), ?.copyWith(fontSize: textTheme.titleSmall?.fontSize),
decoration: InputDecoration( decoration: InputDecoration(
hintText: l10n.l_search_accounts, hintText: l10n.s_search_accounts,
border: const OutlineInputBorder( border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(32)), borderRadius: BorderRadius.all(Radius.circular(32)),
), ),

View File

@ -73,7 +73,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(renamed); Navigator.of(context).pop(renamed);
showMessage(context, l10n.l_account_renamed); showMessage(context, l10n.s_account_renamed);
} on CancellationException catch (_) { } on CancellationException catch (_) {
// ignored // ignored
} catch (e) { } catch (e) {
@ -127,12 +127,12 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
final isValid = isUnique && isValidFormat; final isValid = isUnique && isValidFormat;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_rename_account), title: Text(l10n.s_rename_account),
actions: [ actions: [
TextButton( TextButton(
onPressed: didChange && isValid ? _submit : null, onPressed: didChange && isValid ? _submit : null,
key: keys.saveButton, key: keys.saveButton,
child: Text(l10n.w_save), child: Text(l10n.s_save),
), ),
], ],
child: Padding( child: Padding(
@ -151,7 +151,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
key: keys.issuerField, key: keys.issuerField,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_issuer_optional, labelText: l10n.s_issuer_optional,
helperText: '', // Prevents dialog resizing when disabled helperText: '', // Prevents dialog resizing when disabled
prefixIcon: const Icon(Icons.business_outlined), prefixIcon: const Icon(Icons.business_outlined),
), ),
@ -170,7 +170,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
key: keys.nameField, key: keys.nameField,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.l_account_name, labelText: l10n.s_account_name,
helperText: '', // Prevents dialog resizing when disabled helperText: '', // Prevents dialog resizing when disabled
errorText: !isValidFormat errorText: !isValidFormat
? l10n.l_account_name_required ? l10n.l_account_name_required

View File

@ -32,7 +32,7 @@ class ResetDialog extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.l_factory_reset), title: Text(l10n.s_factory_reset),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@ -42,7 +42,7 @@ class ResetDialog extends ConsumerWidget {
showMessage(context, l10n.l_oath_application_reset); showMessage(context, l10n.l_oath_application_reset);
}); });
}, },
child: Text(l10n.w_reset), child: Text(l10n.s_reset),
), ),
], ],
child: Padding( child: Padding(

View File

@ -79,8 +79,8 @@ class _UnlockFormState extends ConsumerState<UnlockForm> {
obscureText: _isObscure, obscureText: _isObscure,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: l10n.w_password, labelText: l10n.s_password,
errorText: _passwordIsWrong ? l10n.l_wrong_password : null, errorText: _passwordIsWrong ? l10n.s_wrong_password : null,
helperText: '', // Prevents resizing when errorText shown helperText: '', // Prevents resizing when errorText shown
prefixIcon: const Icon(Icons.password_outlined), prefixIcon: const Icon(Icons.password_outlined),
suffixIcon: IconButton( suffixIcon: IconButton(
@ -111,7 +111,7 @@ class _UnlockFormState extends ConsumerState<UnlockForm> {
minLeadingWidth: 0, minLeadingWidth: 0,
) )
: CheckboxListTile( : CheckboxListTile(
title: Text(l10n.l_remember_password), title: Text(l10n.s_remember_password),
dense: true, dense: true,
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
value: _remember, value: _remember,
@ -127,7 +127,7 @@ class _UnlockFormState extends ConsumerState<UnlockForm> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: ElevatedButton.icon( child: ElevatedButton.icon(
key: keys.unlockButton, key: keys.unlockButton,
label: Text(l10n.w_unlock), label: Text(l10n.s_unlock),
icon: const Icon(Icons.lock_open), icon: const Icon(Icons.lock_open),
onPressed: _passwordController.text.isNotEmpty ? _submit : null, onPressed: _passwordController.text.isNotEmpty ? _submit : null,
), ),

View File

@ -39,7 +39,7 @@ class SettingsPage extends ConsumerWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final enableTranslations = ref.watch(communityTranslationsProvider); final enableTranslations = ref.watch(communityTranslationsProvider);
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.w_settings), title: Text(l10n.s_settings),
child: Theme( child: Theme(
// Make the headers use the primary color to pop a bit. // Make the headers use the primary color to pop a bit.
// Once M3 is implemented this will probably not be needed. // Once M3 is implemented this will probably not be needed.
@ -52,9 +52,9 @@ class SettingsPage extends ConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTitle(l10n.w_appearance), ListTitle(l10n.s_appearance),
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: Text(l10n.l_system_default), title: Text(l10n.s_system_default),
value: ThemeMode.system, value: ThemeMode.system,
groupValue: themeMode, groupValue: themeMode,
onChanged: (mode) { onChanged: (mode) {
@ -63,7 +63,7 @@ class SettingsPage extends ConsumerWidget {
}, },
), ),
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: Text(l10n.l_light_mode), title: Text(l10n.s_light_mode),
value: ThemeMode.light, value: ThemeMode.light,
groupValue: themeMode, groupValue: themeMode,
onChanged: (mode) { onChanged: (mode) {
@ -72,7 +72,7 @@ class SettingsPage extends ConsumerWidget {
}, },
), ),
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: Text(l10n.l_dark_mode), title: Text(l10n.s_dark_mode),
value: ThemeMode.dark, value: ThemeMode.dark,
groupValue: themeMode, groupValue: themeMode,
onChanged: (mode) { onChanged: (mode) {
@ -84,7 +84,7 @@ class SettingsPage extends ConsumerWidget {
basicLocaleListResolution(window.locales, officialLocales) != basicLocaleListResolution(window.locales, officialLocales) !=
basicLocaleListResolution( basicLocaleListResolution(
window.locales, AppLocalizations.supportedLocales)) ...[ window.locales, AppLocalizations.supportedLocales)) ...[
ListTitle(l10n.w_language), ListTitle(l10n.s_language),
SwitchListTile( SwitchListTile(
title: Text(l10n.l_enable_community_translations), title: Text(l10n.l_enable_community_translations),
subtitle: Text(l10n.p_community_translations_desc), subtitle: Text(l10n.p_community_translations_desc),

View File

@ -62,8 +62,8 @@ class _ResponsiveDialogState extends State<ResponsiveDialog> {
} else { } else {
// Dialog // Dialog
final cancelText = widget.onCancel == null && widget.actions.isEmpty final cancelText = widget.onCancel == null && widget.actions.isEmpty
? l10n.w_close ? l10n.s_close
: l10n.w_cancel; : l10n.s_cancel;
return AlertDialog( return AlertDialog(
title: widget.title, title: widget.title,
titlePadding: const EdgeInsets.only(top: 24, left: 18, right: 18), titlePadding: const EdgeInsets.only(top: 24, left: 18, right: 18),

View File

@ -38,7 +38,7 @@ InputCounterWidgetBuilder buildByteCounterFor(String currentValue) =>
return Text( return Text(
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '', maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '',
style: style, style: style,
semanticsLabel: AppLocalizations.of(context)!.l_character_count, semanticsLabel: AppLocalizations.of(context)!.s_character_count,
); );
}; };