This commit is contained in:
Adam Velebil 2023-12-12 16:29:28 +01:00
commit b63fb5ba9e
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
15 changed files with 183 additions and 15 deletions

View File

@ -30,6 +30,7 @@ jobs:
apt-get install -qq git python$PYVER_MINOR-dev python$PYVER_MINOR-venv
git config --global --add safe.directory "$GITHUB_WORKSPACE"
ln -s `which python$PYVER_MINOR` /usr/local/bin/python
ln -s `which python$PYVER_MINOR` /usr/local/bin/python3
PYVER_TEMP=`/usr/local/bin/python --version`
export PYVERINST=${PYVER_TEMP#* }
echo "PYVERINST=$PYVERINST" >> $GITHUB_ENV

View File

@ -16,6 +16,12 @@ repos:
language: script
entry: arb_reformatter.py
require_serial: true
- id: update-android-strings
name: update-android-strings
files: \.arb$
language: script
entry: update_android_strings.py
require_serial: true
# Python
- repo: local

View File

@ -33,6 +33,9 @@ class AppPreferences(context: Context) {
const val PREF_CLIP_KBD_LAYOUT = "flutter.prefClipKbdLayout"
const val DEFAULT_CLIP_KBD_LAYOUT = "US"
const val PREF_ENABLE_COMMUNITY_TRANSLATIONS =
"flutter.APP_STATE_ENABLE_COMMUNITY_TRANSLATIONS"
}
private val logger = LoggerFactory.getLogger(AppPreferences::class.java)
@ -66,6 +69,9 @@ class AppPreferences(context: Context) {
val openAppOnUsb: Boolean
get() = prefs.getBoolean(PREF_USB_OPEN_APP, false)
val communityTranslationsEnabled: Boolean
get() = prefs.getBoolean(PREF_ENABLE_COMMUNITY_TRANSLATIONS, false)
fun registerListener(listener: OnSharedPreferenceChangeListener) {
logger.debug("registering change listener")
prefs.registerOnSharedPreferenceChangeListener(listener)

View File

@ -24,13 +24,12 @@ import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import com.yubico.authenticator.ndef.KeyboardLayout
import com.yubico.yubikit.core.util.NdefUtils
import org.slf4j.LoggerFactory
import java.nio.charset.StandardCharsets
import java.util.Locale
typealias ResourceId = Int
@ -66,8 +65,8 @@ class NdefActivity : Activity() {
compatUtil.until(Build.VERSION_CODES.TIRAMISU) {
showToast(
when (otpSlotContent.type) {
OtpType.Otp -> R.string.otp_success_set_otp_to_clipboard
OtpType.Password -> R.string.otp_success_set_password_to_clipboard
OtpType.Otp -> R.string.p_ndef_set_otp
OtpType.Password -> R.string.p_ndef_set_password
}, Toast.LENGTH_SHORT
)
}
@ -77,16 +76,19 @@ class NdefActivity : Activity() {
illegalArgumentException.message ?: "Failure when handling YubiKey OTP",
illegalArgumentException
)
showToast(R.string.otp_parse_failure, Toast.LENGTH_LONG)
showToast(R.string.p_ndef_parse_failure, Toast.LENGTH_LONG)
} catch (_: UnsupportedOperationException) {
showToast(R.string.otp_set_clip_failure, Toast.LENGTH_LONG)
showToast(R.string.p_ndef_set_clip_failure, Toast.LENGTH_LONG)
}
}
if (appPreferences.openAppOnNfcTap) {
val mainAppIntent = Intent(this, MainActivity::class.java).apply {
// Pass the NFC Tag to the main Activity.
putExtra(NfcAdapter.EXTRA_TAG, intent.parcelableExtra<Tag>(NfcAdapter.EXTRA_TAG))
putExtra(
NfcAdapter.EXTRA_TAG,
intent.parcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
)
}
startActivity(mainAppIntent)
}
@ -96,7 +98,15 @@ class NdefActivity : Activity() {
}
private fun showToast(value: ResourceId, length: Int) {
Toast.makeText(this, value, length).show()
val context = if (appPreferences.communityTranslationsEnabled)
this
else {
// always use 'us' locale
val configuration = resources.configuration
configuration.setLocale(Locale.US)
createConfigurationContext(configuration)
}
Toast.makeText(context, value, length).show()
}
private fun parseOtpFromIntent(): OtpSlotValue {

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<resources />

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<resources />

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<resources />

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="p_ndef_set_otp">OTP zostało skopiowane do schowka.</string>
<string name="p_ndef_set_password">Hasło statyczne zostało skopiowane do schowka.</string>
<string name="p_ndef_parse_failure">Błąd czytania OTP z YubiKey.</string>
<string name="p_ndef_set_clip_failure">Błąd kopiowania OTP do schowka.</string>
</resources>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_label">Yubico Authenticator</string>
<string name="otp_success_set_otp_to_clipboard">Successfully copied OTP code from YubiKey to clipboard.</string>
<string name="otp_success_set_password_to_clipboard">Successfully copied password from YubiKey to clipboard.</string>
<string name="otp_parse_failure">Failed to parse OTP code from YubiKey.</string>
<string name="otp_set_clip_failure">Failed to access clipboard when trying to copy OTP code from YubiKey.</string>
<string name="app_label" translatable="false">Yubico Authenticator</string>
<string name="p_ndef_set_otp">Successfully copied OTP code from YubiKey to clipboard.</string>
<string name="p_ndef_set_password">Successfully copied password from YubiKey to clipboard.</string>
<string name="p_ndef_parse_failure">Failed to parse OTP code from YubiKey.</string>
<string name="p_ndef_set_clip_failure">Failed to access clipboard when trying to copy OTP code from YubiKey.</string>
</resources>

View File

@ -626,5 +626,11 @@
"s_nfc_dialog_oath_failure": null,
"s_nfc_dialog_oath_add_multiple_accounts": null,
"@_ndef": {},
"p_ndef_set_otp": null,
"p_ndef_set_password": null,
"p_ndef_parse_failure": null,
"p_ndef_set_clip_failure": null,
"@_eof": {}
}

View File

@ -626,5 +626,11 @@
"s_nfc_dialog_oath_failure": "OATH operation failed",
"s_nfc_dialog_oath_add_multiple_accounts": "Action: add multiple accounts",
"@_ndef": {},
"p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.",
"p_ndef_set_password": "Successfully copied password from YubiKey to clipboard.",
"p_ndef_parse_failure": "Failed to parse OTP code from YubiKey.",
"p_ndef_set_clip_failure": "Failed to access clipboard when trying to copy OTP code from YubiKey.",
"@_eof": {}
}

View File

@ -626,5 +626,11 @@
"s_nfc_dialog_oath_failure": "Échec de l'opération OATH",
"s_nfc_dialog_oath_add_multiple_accounts": "Action: ajouter plusieurs comptes",
"@_ndef": {},
"p_ndef_set_otp": null,
"p_ndef_set_password": null,
"p_ndef_parse_failure": null,
"p_ndef_set_clip_failure": null,
"@_eof": {}
}

View File

@ -626,5 +626,11 @@
"s_nfc_dialog_oath_failure": "OATH操作は失敗しました",
"s_nfc_dialog_oath_add_multiple_accounts": "操作:複数アカウントの追加",
"@_ndef": {},
"p_ndef_set_otp": null,
"p_ndef_set_password": null,
"p_ndef_parse_failure": null,
"p_ndef_set_clip_failure": null,
"@_eof": {}
}

View File

@ -626,5 +626,11 @@
"s_nfc_dialog_oath_failure": "Operacja OATH nie powiodła się",
"s_nfc_dialog_oath_add_multiple_accounts": "Działanie: dodawanie wielu kont",
"@_ndef": {},
"p_ndef_set_otp": "OTP zostało skopiowane do schowka.",
"p_ndef_set_password": "Hasło statyczne zostało skopiowane do schowka.",
"p_ndef_parse_failure": "Błąd czytania OTP z YubiKey.",
"p_ndef_set_clip_failure": "Błąd kopiowania OTP do schowka.",
"@_eof": {}
}

102
update_android_strings.py Executable file
View File

@ -0,0 +1,102 @@
#!/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.
"""Rebuild Android String resources from ARB files."""
import json
import os
import xml.etree.ElementTree as ET
from os import path as p
def read_arb_file(file_path):
"""Load translations from flutter ARB file."""
with open(file_path, "r", encoding="utf-8") as file:
return json.load(file)
def get_lang_file_dir(lang):
"""Return path of Android resource directory for lang."""
return (
f"android/app/src/main/res/values-{lang}"
if lang != "en"
else "android/app/src/main/res/values"
)
def get_lang_file(lang):
"""Return path of Android string resource file for lang."""
return p.join(get_lang_file_dir(lang), "strings.xml")
def process_android_res(lang, arb, keys_to_translate):
"""Generate or update Android string resource for lang.
Parameters
----------
lang : str
language code
arb : dict
content of flutter ARB file
keys_to_translate : list
string resources which will be generated or updated
"""
res_dir = get_lang_file_dir(lang)
if not p.exists(res_dir):
os.makedirs(res_dir)
res_path = get_lang_file(lang)
res = (
ET.parse(res_path).getroot() if p.exists(res_path) else ET.Element("resources")
)
for key in keys_to_translate:
# only add the string if translation exists in arb
if key in arb.keys() and arb[key] is not None:
existing = res.find(f"./string[@name='{key}']")
if existing is not None:
existing.text = arb[key]
else:
ET.SubElement(res, "string", name=f"{key}").text = arb[key]
tree = ET.ElementTree(res)
ET.indent(tree, " ")
tree.write(res_path, encoding="utf-8", xml_declaration=True)
return True
def get_english_strings():
"""Extract translatable strings from English Android string resource."""
strings_en = "android/app/src/main/res/values/strings.xml"
resources_en = ET.parse(strings_en).getroot()
return [
key.attrib.get("name")
for key in resources_en
if key.attrib.get("translatable") in [None, True]
]
if __name__ == "__main__":
arb_files = "lib/l10n"
english_strings = get_english_strings()
for arb_file in os.listdir(arb_files):
if arb_file.startswith("app_") and arb_file.endswith(".arb"):
lang = arb_file.split("_")[1].split(".")[0]
arb_path = p.join(arb_files, arb_file)
arb = read_arb_file(arb_path)
if process_android_res(lang, arb, english_strings):
print(f"Processed: {get_lang_file(lang)}")