feat: translate using the i18n module

This commit is contained in:
topsworld 2025-01-13 22:18:21 +08:00
parent 86e0276108
commit 820d5e1e1b
15 changed files with 243 additions and 147 deletions

View File

@ -91,7 +91,8 @@ from .miot.miot_cloud import MIoTHttpClient, MIoTOauthClient
from .miot.miot_storage import MIoTStorage, MIoTCert
from .miot.miot_mdns import MipsService
from .miot.web_pages import oauth_redirect_page
from .miot.miot_error import MIoTConfigError, MIoTError, MIoTOauthError
from .miot.miot_error import (
MIoTConfigError, MIoTError, MIoTErrorCode, MIoTOauthError)
from .miot.miot_i18n import MIoTI18n
from .miot.miot_network import MIoTNetwork
from .miot.miot_client import MIoTClient, get_miot_instance_async
@ -430,6 +431,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
redirect_url=self._oauth_redirect_url_full)
self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = (
miot_oauth.state)
self.hass.data[DOMAIN][self._virtual_did]['i18n'] = (
self._miot_i18n)
_LOGGER.info(
'async_step_oauth, oauth_url: %s', self._cc_oauth_auth_url)
webhook_async_unregister(
@ -1152,6 +1155,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
redirect_url=self._oauth_redirect_url_full)
self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = (
self._miot_oauth.state)
self.hass.data[DOMAIN][self._virtual_did]['i18n'] = (
self._miot_i18n)
_LOGGER.info(
'async_step_oauth, oauth_url: %s', self._cc_oauth_auth_url)
webhook_async_unregister(
@ -1967,29 +1972,61 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
async def _handle_oauth_webhook(hass, webhook_id, request):
"""Webhook to handle oauth2 callback."""
# pylint: disable=inconsistent-quotes
i18n: MIoTI18n = hass.data[DOMAIN][webhook_id].get('i18n', None)
try:
data = dict(request.query)
if data.get('code', None) is None or data.get('state', None) is None:
raise MIoTConfigError('invalid oauth code')
raise MIoTConfigError(
'invalid oauth code or state',
MIoTErrorCode.CODE_CONFIG_INVALID_INPUT)
if data['state'] != hass.data[DOMAIN][webhook_id]['oauth_state']:
raise MIoTConfigError(
f'invalid oauth state, '
f'{hass.data[DOMAIN][webhook_id]["oauth_state"]}, '
f'{data["state"]}')
f'inconsistent state, '
f'{hass.data[DOMAIN][webhook_id]["oauth_state"]}!='
f'{data["state"]}', MIoTErrorCode.CODE_CONFIG_INVALID_STATE)
fut_oauth_code: asyncio.Future = hass.data[DOMAIN][webhook_id].pop(
'fut_oauth_code', None)
fut_oauth_code.set_result(data['code'])
_LOGGER.info('webhook code: %s', data['code'])
success_trans: dict = {}
if i18n:
success_trans = i18n.translate(
'oauth2.success') or {} # type: ignore
# Delete
del hass.data[DOMAIN][webhook_id]['oauth_state']
del hass.data[DOMAIN][webhook_id]['i18n']
return web.Response(
body=await oauth_redirect_page(
hass.config.language, 'success'), content_type='text/html')
title=success_trans.get('title', 'Success'),
content=success_trans.get(
'content', (
'Please close this page and return to the account '
'authentication page to click NEXT')),
button=success_trans.get('button', 'Close Page'),
success=True,
), content_type='text/html')
except MIoTConfigError:
except Exception as err: # pylint: disable=broad-exception-caught
fail_trans: dict = {}
err_msg: str = str(err)
if i18n:
if isinstance(err, MIoTConfigError):
err_msg = i18n.translate(
f'oauth2.error_msg.{err.code.value}'
) or err.message # type: ignore
fail_trans = i18n.translate('oauth2.fail') or {} # type: ignore
return web.Response(
body=await oauth_redirect_page(hass.config.language, 'fail'),
body=await oauth_redirect_page(
title=fail_trans.get('title', 'Authentication Failed'),
content=str(fail_trans.get('content', (
'{error_msg}, Please close this page and return to the '
'account authentication page to click the authentication '
'link again.'))).replace('{error_msg}', err_msg),
button=fail_trans.get('button', 'Close Page'),
success=False),
content_type='text/html')

View File

@ -64,6 +64,22 @@
"net_unavailable": "Schnittstelle nicht verfügbar"
}
},
"oauth2": {
"success": {
"title": "Authentifizierung erfolgreich",
"content": "Bitte schließen Sie diese Seite und kehren Sie zur Kontoauthentifizierungsseite zurück, um auf „Weiter“ zu klicken.",
"button": "Schließen"
},
"fail": {
"title": "Authentifizierung fehlgeschlagen",
"content": "{error_msg}, bitte schließen Sie diese Seite und kehren Sie zur Kontoauthentifizierungsseite zurück, um den Authentifizierungslink erneut zu klicken.",
"button": "Schließen"
},
"error_msg": {
"-10100": "Ungültige Antwortparameter ('code' oder 'state' Feld ist leer)",
"-10101": "Übergebenes 'state' Feld stimmt nicht überein"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Ungültige Authentifizierungsinformationen, Cloud-Verbindung nicht verfügbar, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen', um die Authentifizierung erneut durchzuführen",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interface unavailable"
}
},
"oauth2": {
"success": {
"title": "Authentication Successful",
"content": "Please close this page and return to the account authentication page to click 'Next'.",
"button": "Close"
},
"fail": {
"title": "Authentication Failed",
"content": "{error_msg}, please close this page and return to the account authentication page to click the authentication link again.",
"button": "Close"
},
"error_msg": {
"-10100": "Invalid response parameters ('code' or 'state' field is empty)",
"-10101": "Passed-in 'state' field mismatch"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Authentication information is invalid, cloud link will be unavailable, please enter the Xiaomi Home integration page, click 'Options' to re-authenticate",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interfaz no disponible"
}
},
"oauth2": {
"success": {
"title": "Autenticación exitosa",
"content": "Por favor, cierre esta página y regrese a la página de autenticación de la cuenta para hacer clic en 'Siguiente'.",
"button": "Cerrar"
},
"fail": {
"title": "Autenticación fallida",
"content": "{error_msg}, por favor, cierre esta página y regrese a la página de autenticación de la cuenta para hacer clic en el enlace de autenticación nuevamente.",
"button": "Cerrar"
},
"error_msg": {
"-10100": "Parámetros de respuesta inválidos ('code' o 'state' está vacío)",
"-10101": "El campo 'state' proporcionado no coincide"
}
},
"miot": {
"client": {
"invalid_oauth_info": "La información de autenticación es inválida, la conexión en la nube no estará disponible, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones' para volver a autenticar",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interface non disponible"
}
},
"oauth2": {
"success": {
"title": "Authentification réussie",
"content": "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer sur 'Suivant'.",
"button": "Fermer"
},
"fail": {
"title": "Échec de l'authentification",
"content": "{error_msg}, veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer à nouveau sur le lien d'authentification.",
"button": "Fermer"
},
"error_msg": {
"-10100": "Paramètres de réponse invalides ('code' ou 'state' est vide)",
"-10101": "Le champ 'state' transmis ne correspond pas"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informations d'authentification non valides, le lien cloud ne sera pas disponible, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur \"Options\" pour vous réauthentifier",

View File

@ -64,6 +64,22 @@
"net_unavailable": "インターフェースが利用できません"
}
},
"oauth2": {
"success": {
"title": "認証成功",
"content": "このページを閉じて、アカウント認証ページに戻り、「次へ」をクリックしてください。",
"button": "閉じる"
},
"fail": {
"title": "認証失敗",
"content": "{error_msg}、このページを閉じて、アカウント認証ページに戻り、再度認証リンクをクリックしてください。",
"button": "閉じる"
},
"error_msg": {
"-10100": "無効な応答パラメータ('code'または'state'フィールドが空です)",
"-10101": "渡された'state'フィールドが一致しません"
}
},
"miot": {
"client": {
"invalid_oauth_info": "認証情報が無効です。クラウドリンクは利用できません。Xiaomi Home統合ページに入り、[オプション]をクリックして再認証してください",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interface niet beschikbaar"
}
},
"oauth2": {
"success": {
"title": "Authenticatie geslaagd",
"content": "Sluit deze pagina en ga terug naar de accountauthenticatiepagina om op 'Volgende' te klikken.",
"button": "Sluiten"
},
"fail": {
"title": "Authenticatie mislukt",
"content": "{error_msg}, sluit deze pagina en ga terug naar de accountauthenticatiepagina om opnieuw op de authenticatielink te klikken.",
"button": "Sluiten"
},
"error_msg": {
"-10100": "Ongeldige antwoordparameters ('code' of 'state' veld is leeg)",
"-10101": "Doorgegeven 'state' veld komt niet overeen"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Authenticatie-informatie is ongeldig, cloudverbinding zal niet beschikbaar zijn. Ga naar de Xiaomi Home-integratiepagina en klik op 'Opties' om opnieuw te verifiëren.",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interface indisponível"
}
},
"oauth2": {
"success": {
"title": "Autenticação bem-sucedida",
"content": "Por favor, feche esta página e volte para a página de autenticação da conta para clicar em 'Próximo'.",
"button": "Fechar"
},
"fail": {
"title": "Falha na autenticação",
"content": "{error_msg}, por favor, feche esta página e volte para a página de autenticação da conta para clicar no link de autenticação novamente.",
"button": "Fechar"
},
"error_msg": {
"-10100": "Parâmetros de resposta inválidos ('code' ou 'state' está vazio)",
"-10101": "O campo 'state' fornecido não corresponde"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão com a nuvem estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Interface indisponível"
}
},
"oauth2": {
"success": {
"title": "Autenticação bem-sucedida",
"content": "Por favor, feche esta página e volte para a página de autenticação da conta para clicar em 'Seguinte'.",
"button": "Fechar"
},
"fail": {
"title": "Falha na autenticação",
"content": "{error_msg}, por favor, feche esta página e volte para a página de autenticação da conta para clicar no link de autenticação novamente.",
"button": "Fechar"
},
"error_msg": {
"-10100": "Parâmetros de resposta inválidos ('code' ou 'state' está vazio)",
"-10101": "O campo 'state' fornecido não corresponde"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão na nuvem ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.",

View File

@ -64,6 +64,22 @@
"net_unavailable": "Интерфейс недоступен"
}
},
"oauth2": {
"success": {
"title": "Аутентификация успешна",
"content": "Пожалуйста, закройте эту страницу и вернитесь на страницу аутентификации учетной записи, чтобы нажать 'Далее'.",
"button": "Закрыть"
},
"fail": {
"title": "Аутентификация не удалась",
"content": "{error_msg}, пожалуйста, закройте эту страницу и вернитесь на страницу аутентификации учетной записи, чтобы снова нажать на ссылку аутентификации.",
"button": "Закрыть"
},
"error_msg": {
"-10100": "Недействительные параметры ответа ('code' или 'state' поле пусто)",
"-10101": "Переданное поле 'state' не совпадает"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Информация об аутентификации недействительна, облако будет недоступно, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции' для повторной аутентификации",

View File

@ -64,6 +64,22 @@
"net_unavailable": "接口不可用"
}
},
"oauth2": {
"success": {
"title": "认证成功",
"content": "请关闭此页面,返回账号认证页面点击“下一步”",
"button": "关闭"
},
"fail": {
"title": "认证失败",
"content": "{error_msg},请关闭此页面,返回账号认证页面重新点击认链接进行认证。",
"button": "关闭"
},
"error_msg": {
"-10100": "无效的响应参数“code”或者“state”字段为空",
"-10101": "传入“state”字段不一致"
}
},
"miot": {
"client": {
"invalid_oauth_info": "认证信息失效,云端链路将不可用,请进入 Xiaomi Home 集成页面,点击“选项”重新认证",

View File

@ -64,6 +64,22 @@
"net_unavailable": "接口不可用"
}
},
"oauth2": {
"success": {
"title": "認證成功",
"content": "請關閉此頁面,返回帳號認證頁面點擊“下一步”",
"button": "關閉"
},
"fail": {
"title": "認證失敗",
"content": "{error_msg},請關閉此頁面,返回帳號認證頁面重新點擊認鏈接進行認證。",
"button": "關閉"
},
"error_msg": {
"-10100": "無效的響應參數('code'或者'state'字段為空)",
"-10101": "傳入的'state'字段不一致"
}
},
"miot": {
"client": {
"invalid_oauth_info": "認證信息失效,雲端鏈路將不可用,請進入 Xiaomi Home 集成頁面,點擊“選項”重新認證",

View File

@ -72,6 +72,8 @@ class MIoTErrorCode(Enum):
# MIoT ev error code, -10080
# Mips service error code, -10090
# Config flow error code, -10100
CODE_CONFIG_INVALID_INPUT = -10100
CODE_CONFIG_INVALID_STATE = -10101
# Options flow error code , -10110
# MIoT lan error code, -10120
CODE_LAN_UNAVAILABLE = -10120

View File

@ -7,7 +7,7 @@
<link as="style"
href="https://font.sec.miui.com/font/css?family=MiSans:300,400,500,600,700:Chinese_Simplify,Chinese_Traditional,Latin&amp;display=swap"
rel="preload">
<title></title>
<title>TITLE_PLACEHOLDER</title>
<style>
body {
background: white;
@ -115,145 +115,21 @@
</div>
<!-- TITLE -->
<div class="title-frame">
<a id="titleArea"></a>
<a id="titleArea">TITLE_PLACEHOLDER</a>
</div>
<!-- CONTENT -->
<div class="content-frame">
<a id="contentArea"></a>
<a id="contentArea">CONTENT_PLACEHOLDER</a>
</div>
<!-- BUTTON -->
<button onClick="window.close();" id="buttonArea"></button>
<button onClick="window.close();" id="buttonArea">BUTTON_PLACEHOLDER</button>
</div>
<script>
// get language (user language -> system language)
const systemLanguage = 'LANG_PLACEHOLDER'; // DO NOT edit.
const locale = (localStorage.getItem('selectedLanguage') ?? systemLanguage).replaceAll('"', '');
const language = locale.split('-')[0].trim();
const status = 'STATUS_PLACEHOLDER'; // DO NOT edit.
console.log(locale);
/**
* @type {{
* [locale: string] : {
* success: {
* title:string;
* content:string;
* button:string;
* };
* fail: {
* title:string;
* content:string;
* button:string;
* };
* };
* }}
*/
const translations = {
zh: {
success: {
title: "认证完成",
content: "请关闭此页面,返回账号认证页面点击“下一步”",
button: "关闭页面"
},
fail: {
title: "认证失败",
content: "请关闭此页面,返回账号认证页面重新点击认链接进行认证。",
button: "关闭页面"
}
},
'zh-Hant': {
success: {
title: "認證完成",
content: "請關閉此頁面,返回帳號認證頁面點擊「下一步」。",
button: "關閉頁面"
},
fail: {
title: "認證失敗",
content: "請關閉此頁面,返回帳號認證頁面重新點擊認鏈接進行認證。",
button: "關閉頁面"
}
},
en: {
success: {
title: "Authentication Completed",
content: "Please close this page and return to the account authentication page to click NEXT",
button: "Close Page"
},
fail: {
title: "Authentication Failed",
content: "Please close this page and return to the account authentication page to click the authentication link again.",
button: "Close Page"
}
},
fr: {
success: {
title: "Authentification Terminée",
content: "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer sur « SUIVANT »",
button: "Fermer la page"
},
fail: {
title: "Échec de l'Authentification",
content: "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer de nouveau sur le lien d'authentification.",
button: "Fermer la page"
}
},
ru: {
success: {
title: "Подтверждение завершено",
content: "Пожалуйста, закройте эту страницу, вернитесь на страницу аутентификации учетной записи и нажмите кнопку «Далее».",
button: "Закрыть страницу"
},
fail: {
title: "Ошибка аутентификации",
content: "Пожалуйста, закройте эту страницу, вернитесь на страницу аутентификации учетной записи и повторите процесс аутентификации, щелкнув ссылку.",
button: "Закрыть страницу"
}
},
de: {
success: {
title: "Authentifizierung abgeschlossen",
content: "Bitte schließen Sie diese Seite, kehren Sie zur Kontobestätigungsseite zurück und klicken Sie auf „WEITER“.",
button: "Seite schließen"
},
fail: {
title: "Authentifizierung fehlgeschlagen",
content: "Bitte schließen Sie diese Seite, kehren Sie zur Kontobestätigungsseite zurück und wiederholen Sie den Authentifizierungsprozess, indem Sie auf den Link klicken.",
button: "Seite schließen"
}
},
es: {
success: {
title: "Autenticación completada",
content: "Por favor, cierre esta página, regrese a la página de autenticación de la cuenta y haga clic en 'SIGUIENTE'.",
button: "Cerrar página"
},
fail: {
title: "Error de autenticación",
content: "Por favor, cierre esta página, regrese a la página de autenticación de la cuenta y vuelva a hacer clic en el enlace de autenticación.",
button: "Cerrar página"
}
},
ja: {
success: {
title: "認証完了",
content: "このページを閉じて、アカウント認証ページに戻り、「次」をクリックしてください。",
button: "ページを閉じる"
},
fail: {
title: "認証失敗",
content: "このページを閉じて、アカウント認証ページに戻り、認証リンクを再度クリックしてください。",
button: "ページを閉じる"
}
}
if (STATUS_PLACEHOLDER) {
window.opener = null;
window.open('', '_self');
window.close();
}
// insert translate into page / match order: locale > language > english
const translation = (translations[locale] ?? translations[language] ?? translations["en"])[status];
document.title = translation.title;
document.getElementById("titleArea").innerText = translation.title;
document.getElementById("contentArea").innerText = translation.content;
document.getElementById("buttonArea").innerText = translation.button;
window.opener = null;
window.open('', '_self');
window.close();
</script>
</body>

View File

@ -49,23 +49,28 @@ MIoT redirect web pages.
import os
import asyncio
_template = ""
_template = ''
def _load_page_template():
path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"resource/oauth_redirect_page.html")
with open(path, "r", encoding="utf-8") as f:
'resource/oauth_redirect_page.html')
with open(path, 'r', encoding='utf-8') as f:
global _template
_template = f.read()
async def oauth_redirect_page(lang: str, status: str) -> str:
async def oauth_redirect_page(
title: str, content: str, button: str, success: bool
) -> str:
"""Return oauth redirect page."""
if _template == "":
if _template == '':
await asyncio.get_running_loop().run_in_executor(
None, _load_page_template)
web_page = _template.replace("LANG_PLACEHOLDER", lang)
web_page = web_page.replace("STATUS_PLACEHOLDER", status)
web_page = _template.replace('TITLE_PLACEHOLDER', title)
web_page = web_page.replace('CONTENT_PLACEHOLDER', content)
web_page = web_page.replace('BUTTON_PLACEHOLDER', button)
web_page = web_page.replace(
'STATUS_PLACEHOLDER', 'true' if success else 'false')
return web_page