feat: refine 2fa-related i18n (#6228)

#### What type of PR is this?

/area ui
/kind feature
/milestone 2.17.x

#### What this PR does / why we need it:

补充 2FA 相关的 i18n 定义。

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/6211/head
Ryan Wang 2024-07-01 14:41:16 +08:00 committed by GitHub
parent 2c454ccb28
commit f3f48e2753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 285 additions and 39 deletions

View File

@ -1,15 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { setFocus } from "@/formkit/utils/focus";
import { submitForm } from "@formkit/core"; import { submitForm } from "@formkit/core";
import { Toast, VButton } from "@halo-dev/components"; import { Toast, VButton } from "@halo-dev/components";
import axios from "axios"; import axios from "axios";
import qs from "qs"; import qs from "qs";
import { onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "succeed"): void; (event: "succeed"): void;
}>(); }>();
const loading = ref(false);
async function onSubmit({ code }: { code: string }) { async function onSubmit({ code }: { code: string }) {
try { try {
loading.value = true;
const _csrf = document.cookie const _csrf = document.cookie
.split("; ") .split("; ")
.find((row) => row.startsWith("XSRF-TOKEN")) .find((row) => row.startsWith("XSRF-TOKEN"))
@ -36,9 +45,15 @@ async function onSubmit({ code }: { code: string }) {
emit("succeed"); emit("succeed");
} catch (error) { } catch (error) {
Toast.error("验证失败"); Toast.error(t("core.common.toast.validation_failed"));
} finally {
loading.value = false;
} }
} }
onMounted(() => {
setFocus("code");
});
</script> </script>
<template> <template>
@ -54,19 +69,25 @@ async function onSubmit({ code }: { code: string }) {
@keyup.enter="submitForm('mfa-form')" @keyup.enter="submitForm('mfa-form')"
> >
<FormKit <FormKit
id="code"
:classes="{ :classes="{
outer: '!py-0', outer: '!py-0',
}" }"
name="code" name="code"
placeholder="请输入两步验证码" :placeholder="$t('core.login.2fa.fields.code.placeholder')"
validation-label="两步验证码" :validation-label="$t('core.login.2fa.fields.code.label')"
:autofocus="true"
type="text" type="text"
validation="required" validation="required"
> >
</FormKit> </FormKit>
</FormKit> </FormKit>
<VButton class="mt-8" block type="secondary" @click="submitForm('mfa-form')"> <VButton
验证 :loading="loading"
class="mt-8"
block
type="secondary"
@click="submitForm('mfa-form')"
>
{{ $t("core.common.buttons.verify") }}
</VButton> </VButton>
</template> </template>

View File

@ -25,6 +25,11 @@ core:
button: Login button: Login
modal: modal:
title: Re-login title: Re-login
2fa:
fields:
code:
placeholder: Two-step verification code
label: Two-step verification code
signup: signup:
title: Sign up title: Sign up
fields: fields:
@ -1161,6 +1166,7 @@ core:
detail: Detail detail: Detail
notification-preferences: Notification Preferences notification-preferences: Notification Preferences
pat: Personal Access Tokens pat: Personal Access Tokens
2fa: 2FA
devices: Devices devices: Devices
actions: actions:
update_profile: update_profile:
@ -1194,6 +1200,49 @@ core:
title: Verify email title: Verify email
email_verified: email_verified:
tooltip: Verified tooltip: Verified
2fa:
operations:
enable:
button: Enable 2FA
title: Enable 2FA
disable:
title: Disable 2FA
disable_totp:
title: Disable TOTP
password_validation_form:
fields:
password:
label: Password
help: Login password of the current account
methods:
title: Two-factor methods
totp:
title: TOTP
description: Configure two-step verification with TOTP application
fields:
status:
configured: Configured
not_configured: Not configured
operations:
reconfigure:
button: Reconfigure
configure:
button: Configure
title: TOTP configuration
fields:
code:
label: Verification code
help: >-
6-digit verification code obtained from the validator
application
password:
label: Password
help: Login password of the current account
qrcode:
label: "Use the validator application to scan the QR code below:"
manual:
label: "If you can't scan the QR code, click to view the alternative steps."
help: "Manually configure the validator application with the following code:"
pat: pat:
operations: operations:
delete: delete:
@ -1707,6 +1756,8 @@ core:
access: Access access: Access
schedule_publish: Schedule publish schedule_publish: Schedule publish
revoke: Revoke revoke: Revoke
disable: Disable
enable: Enable
radio: radio:
"yes": "Yes" "yes": "Yes"
"no": "No" "no": "No"
@ -1739,6 +1790,8 @@ core:
not_found: Resource not found not_found: Resource not found
server_internal_error: Internal server error server_internal_error: Internal server error
unknown_error: Unknown error unknown_error: Unknown error
disable_success: Disabled successfully
enable_success: Enabled successfully
dialog: dialog:
titles: titles:
tip: Tip tip: Tip

View File

@ -25,6 +25,11 @@ core:
button: 登录 button: 登录
modal: modal:
title: 重新登录 title: 重新登录
2fa:
fields:
code:
placeholder: 请输入两步验证码
label: 两步验证码
signup: signup:
title: 注册 title: 注册
fields: fields:
@ -1086,6 +1091,7 @@ core:
detail: 详情 detail: 详情
notification-preferences: 通知配置 notification-preferences: 通知配置
pat: 个人令牌 pat: 个人令牌
2fa: 两步验证
devices: 登录设备 devices: 登录设备
actions: actions:
update_profile: update_profile:
@ -1115,6 +1121,47 @@ core:
description: 电子邮箱地址还未验证,点击下方按钮进行验证 description: 电子邮箱地址还未验证,点击下方按钮进行验证
email_verified: email_verified:
tooltip: 已验证 tooltip: 已验证
2fa:
operations:
enable:
button: 启用两步验证
title: 启用两步验证
disable:
title: 禁用两步验证
disable_totp:
title: 停用 TOTP
password_validation_form:
fields:
password:
label: 验证密码
help: 当前账号的登录密码
methods:
title: 验证方式
totp:
title: TOTP
description: 使用 TOTP 应用程序配置两步验证
fields:
status:
configured: 已配置
not_configured: 未配置
operations:
reconfigure:
button: 重新配置
configure:
button: 配置
title: TOTP 配置
fields:
code:
label: 验证码
help: 从验证器应用获得的 6 位验证码
password:
label: 验证密码
help: 当前账号的登录密码
qrcode:
label: 使用验证器应用扫描下方二维码:
manual:
label: 如果无法扫描二维码,点击查看代替步骤
help: 使用以下代码手动配置验证器应用:
pat: pat:
operations: operations:
delete: delete:
@ -1624,6 +1671,8 @@ core:
access: 访问 access: 访问
schedule_publish: 定时发布 schedule_publish: 定时发布
revoke: 撤销 revoke: 撤销
disable: 禁用
enable: 启用
radio: radio:
"yes": "yes":
"no": "no":
@ -1656,6 +1705,8 @@ core:
not_found: 资源不存在 not_found: 资源不存在
server_internal_error: 服务器内部错误 server_internal_error: 服务器内部错误
unknown_error: 未知错误 unknown_error: 未知错误
disable_success: 禁用成功
enable_success: 啟用成功
dialog: dialog:
titles: titles:
tip: 提示 tip: 提示

View File

@ -25,6 +25,11 @@ core:
button: 登入 button: 登入
modal: modal:
title: 重新登入 title: 重新登入
2fa:
fields:
code:
placeholder: 請輸入兩步驟驗證碼
label: 兩步驟驗證碼
signup: signup:
title: 註冊 title: 註冊
fields: fields:
@ -1066,6 +1071,7 @@ core:
detail: 詳情 detail: 詳情
notification-preferences: 通知配置 notification-preferences: 通知配置
pat: 個人令牌 pat: 個人令牌
2fa: 两步验证
devices: 登錄設備 devices: 登錄設備
actions: actions:
update_profile: update_profile:
@ -1095,6 +1101,47 @@ core:
title: 驗證電子郵件信箱 title: 驗證電子郵件信箱
email_verified: email_verified:
tooltip: 已驗證 tooltip: 已驗證
2fa:
operations:
enable:
button: 启用两步验证
title: 启用两步验证
disable:
title: 禁用两步验证
disable_totp:
title: 停用 TOTP
password_validation_form:
fields:
password:
label: 验证密碼
help: 目前帳號的登入密碼
methods:
title: 驗證方法
totp:
title: TOTP
description: 使用 TOTP 應用程式設定兩步驟驗證
fields:
status:
configured: 已配置
not_configured: 未配置
operations:
reconfigure:
button: 重新配置
configure:
button: 配置
title: TOTP 配置
fields:
code:
label: 驗證碼
help: 從驗證器應用程式取得的 6 位驗證碼
password:
label: 验证密碼
help: 目前帳號的登入密碼
qrcode:
label: 使用驗證器應用程式掃描下面的二維碼:
manual:
label: 如果您無法掃描二維碼,請按一下查看替代步驟。
help: 使用以下程式碼手動配置驗證器應用程式:
pat: pat:
operations: operations:
delete: delete:
@ -1581,6 +1628,8 @@ core:
access: 訪問 access: 訪問
schedule_publish: 定時發佈 schedule_publish: 定時發佈
revoke: 撤銷 revoke: 撤銷
disable: 禁用
enable: 启用
radio: radio:
"yes": "yes":
"no": "no":
@ -1613,6 +1662,8 @@ core:
not_found: 資源不存在 not_found: 資源不存在
server_internal_error: 伺服器內部錯誤 server_internal_error: 伺服器內部錯誤
unknown_error: 未知錯誤 unknown_error: 未知錯誤
disable_success: 禁用成功
enable_success: 啟用成功
dialog: dialog:
titles: titles:
tip: 提示 tip: 提示

View File

@ -70,7 +70,7 @@ const tabs = ref<UserProfileTab[]>([
}, },
{ {
id: "2fa", id: "2fa",
label: "两步验证", label: t("core.uc_profile.tabs.2fa"),
component: markRaw(TwoFactor), component: markRaw(TwoFactor),
priority: 40, priority: 40,
}, },

View File

@ -51,7 +51,9 @@ const totpDeletionModalVisible = ref(false);
:checked="settings?.enabled" :checked="settings?.enabled"
@change="onEnabledChange" @change="onEnabledChange"
/> />
<span class="text-sm font-medium text-gray-700">启用两步验证</span> <span class="text-sm font-medium text-gray-700">
{{ $t("core.uc_profile.2fa.operations.enable.button") }}
</span>
</label> </label>
</div> </div>
@ -63,7 +65,9 @@ const totpDeletionModalVisible = ref(false);
role="list" role="list"
> >
<li class="bg-gray-50 px-4 py-3"> <li class="bg-gray-50 px-4 py-3">
<span class="text-sm font-semibold text-gray-900">验证方式</span> <span class="text-sm font-semibold text-gray-900">
{{ $t("core.uc_profile.2fa.methods.title") }}
</span>
</li> </li>
<li> <li>
<VEntity> <VEntity>
@ -74,20 +78,36 @@ const totpDeletionModalVisible = ref(false);
</template> </template>
</VEntityField> </VEntityField>
<VEntityField <VEntityField
title="TOTP" :title="$t('core.uc_profile.2fa.methods.totp.title')"
description="使用 TOTP 应用程序配置两步验证" :description="$t('core.uc_profile.2fa.methods.totp.description')"
/> />
</template> </template>
<template #end> <template #end>
<StatusDotField <StatusDotField
:state="settings?.totpConfigured ? 'success' : 'default'" :state="settings?.totpConfigured ? 'success' : 'default'"
:text="settings?.totpConfigured ? '已配置' : '未配置'" :text="
settings?.totpConfigured
? $t(
'core.uc_profile.2fa.methods.totp.fields.status.configured'
)
: $t(
'core.uc_profile.2fa.methods.totp.fields.status.not_configured'
)
"
></StatusDotField> ></StatusDotField>
<VEntityField> <VEntityField>
<template #description> <template #description>
<VSpace> <VSpace>
<VButton size="sm" @click="totpConfigureModalVisible = true"> <VButton size="sm" @click="totpConfigureModalVisible = true">
{{ settings?.totpConfigured ? "重新配置" : "配置" }} {{
settings?.totpConfigured
? $t(
"core.uc_profile.2fa.methods.totp.operations.reconfigure.button"
)
: $t(
"core.uc_profile.2fa.methods.totp.operations.configure.button"
)
}}
</VButton> </VButton>
<VButton <VButton
v-if="settings?.totpConfigured" v-if="settings?.totpConfigured"
@ -95,7 +115,7 @@ const totpDeletionModalVisible = ref(false);
type="danger" type="danger"
@click="totpDeletionModalVisible = true" @click="totpDeletionModalVisible = true"
> >
停用 {{ $t("core.common.buttons.disable") }}
</VButton> </VButton>
</VSpace> </VSpace>
</template> </template>

View File

@ -17,10 +17,14 @@ function onSubmit({ password }: { password: string }) {
> >
<FormKit <FormKit
type="password" type="password"
label="验证密码" :label="
$t('core.uc_profile.2fa.password_validation_form.fields.password.label')
"
validation="required" validation="required"
name="password" name="password"
help="当前账号的登录密码" :help="
$t('core.uc_profile.2fa.password_validation_form.fields.password.help')
"
autocomplete="current-password" autocomplete="current-password"
></FormKit> ></FormKit>
</FormKit> </FormKit>

View File

@ -5,8 +5,10 @@ import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import { useQRCode } from "@vueuse/integrations/useQRCode"; import { useQRCode } from "@vueuse/integrations/useQRCode";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
@ -32,7 +34,7 @@ const { mutate, isLoading } = useMutation({
}); });
}, },
onSuccess() { onSuccess() {
Toast.success("配置成功"); Toast.success(t("core.common.toast.save_success"));
queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] }); queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] });
modal.value?.close(); modal.value?.close();
}, },
@ -48,20 +50,34 @@ function onSubmit(data: TotpRequest) {
ref="modal" ref="modal"
:width="500" :width="500"
:centered="false" :centered="false"
title="TOTP 配置" :title="$t('core.uc_profile.2fa.methods.totp.operations.configure.title')"
@close="emit('close')" @close="emit('close')"
> >
<div> <div>
<div class="mb-4 space-y-3 border-b border-gray-100 pb-4 text-gray-900"> <div class="mb-4 space-y-3 border-b border-gray-100 pb-4 text-gray-900">
<div class="text-sm font-semibold">使用验证器应用扫描下方二维码</div> <div class="text-sm font-semibold">
{{
$t(
"core.uc_profile.2fa.methods.totp.operations.configure.fields.qrcode.label"
)
}}
</div>
<img :src="qrcode" class="rounded-base border border-gray-100" /> <img :src="qrcode" class="rounded-base border border-gray-100" />
<details> <details>
<summary class="cursor-pointer select-none text-sm text-gray-800"> <summary class="cursor-pointer select-none text-sm text-gray-800">
如果无法扫描二维码点击查看代替步骤 {{
$t(
"core.uc_profile.2fa.methods.totp.operations.configure.fields.manual.label"
)
}}
</summary> </summary>
<div class="mt-3 rounded-base border border-gray-100 p-2"> <div class="mt-3 rounded-base border border-gray-100 p-2">
<span class="text-sm text-gray-600"> <span class="text-sm text-gray-600">
使用以下代码手动配置验证器应用 {{
$t(
"core.uc_profile.2fa.methods.totp.operations.configure.fields.manual.help"
)
}}
</span> </span>
<div class="mt-2"> <div class="mt-2">
<code <code
@ -77,16 +93,32 @@ function onSubmit(data: TotpRequest) {
<FormKit <FormKit
type="number" type="number"
name="code" name="code"
label="验证码" :label="
$t(
'core.uc_profile.2fa.methods.totp.operations.configure.fields.code.label'
)
"
validation="required" validation="required"
help="从验证器应用获得的 6 位验证码" :help="
$t(
'core.uc_profile.2fa.methods.totp.operations.configure.fields.code.help'
)
"
></FormKit> ></FormKit>
<FormKit <FormKit
type="password" type="password"
label="验证密码" :label="
$t(
'core.uc_profile.2fa.methods.totp.operations.configure.fields.password.label'
)
"
validation="required" validation="required"
name="password" name="password"
help="当前账号的登录密码" :help="
$t(
'core.uc_profile.2fa.methods.totp.operations.configure.fields.password.help'
)
"
autocomplete="current-password" autocomplete="current-password"
></FormKit> ></FormKit>
<FormKit <FormKit
@ -103,9 +135,11 @@ function onSubmit(data: TotpRequest) {
type="secondary" type="secondary"
@click="$formkit.submit('totp-form')" @click="$formkit.submit('totp-form')"
> >
完成 {{ $t("core.common.buttons.save") }}
</VButton>
<VButton @click="modal?.close()">
{{ $t("core.common.buttons.close") }}
</VButton> </VButton>
<VButton @click="modal?.close()"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>

View File

@ -3,9 +3,11 @@ import { ucApiClient } from "@halo-dev/api-client";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components"; import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { useMutation, useQueryClient } from "@tanstack/vue-query"; import { useMutation, useQueryClient } from "@tanstack/vue-query";
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n";
import PasswordValidationForm from "./PasswordValidationForm.vue"; import PasswordValidationForm from "./PasswordValidationForm.vue";
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
@ -23,7 +25,7 @@ const { mutate, isLoading } = useMutation({
}); });
}, },
onSuccess() { onSuccess() {
Toast.success("停用成功"); Toast.success(t("core.common.toast.disable_success"));
queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] }); queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] });
modal.value?.close(); modal.value?.close();
}, },
@ -39,7 +41,7 @@ function onSubmit(password: string) {
ref="modal" ref="modal"
:width="500" :width="500"
:centered="false" :centered="false"
title="停用 TOTP" :title="$t('core.uc_profile.2fa.operations.disable_totp.title')"
@close="emit('close')" @close="emit('close')"
> >
<PasswordValidationForm @submit="onSubmit" /> <PasswordValidationForm @submit="onSubmit" />
@ -50,9 +52,11 @@ function onSubmit(password: string) {
type="danger" type="danger"
@click="$formkit.submit('password-validation-form')" @click="$formkit.submit('password-validation-form')"
> >
停用 {{ $t("core.common.buttons.disable") }}
</VButton>
<VButton @click="modal?.close()">
{{ $t("core.common.buttons.close") }}
</VButton> </VButton>
<VButton @click="modal?.close()"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>

View File

@ -3,9 +3,11 @@ import { ucApiClient } from "@halo-dev/api-client";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components"; import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { useMutation, useQueryClient } from "@tanstack/vue-query"; import { useMutation, useQueryClient } from "@tanstack/vue-query";
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n";
import PasswordValidationForm from "./PasswordValidationForm.vue"; import PasswordValidationForm from "./PasswordValidationForm.vue";
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
@ -23,7 +25,7 @@ const { mutate, isLoading } = useMutation({
}); });
}, },
onSuccess() { onSuccess() {
Toast.success("停用成功"); Toast.success(t("core.common.toast.disable_success"));
queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] }); queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] });
modal.value?.close(); modal.value?.close();
}, },
@ -39,7 +41,7 @@ function onSubmit(password: string) {
ref="modal" ref="modal"
:width="500" :width="500"
:centered="false" :centered="false"
title="停用两步验证" :title="$t('core.uc_profile.2fa.operations.disable.title')"
@close="emit('close')" @close="emit('close')"
> >
<PasswordValidationForm @submit="onSubmit" /> <PasswordValidationForm @submit="onSubmit" />
@ -50,9 +52,11 @@ function onSubmit(password: string) {
type="danger" type="danger"
@click="$formkit.submit('password-validation-form')" @click="$formkit.submit('password-validation-form')"
> >
停用 {{ $t("core.common.buttons.disable") }}
</VButton>
<VButton @click="modal?.close()">
{{ $t("core.common.buttons.close") }}
</VButton> </VButton>
<VButton @click="modal?.close()"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>

View File

@ -3,9 +3,11 @@ import { ucApiClient } from "@halo-dev/api-client";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components"; import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { useMutation, useQueryClient } from "@tanstack/vue-query"; import { useMutation, useQueryClient } from "@tanstack/vue-query";
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n";
import PasswordValidationForm from "./PasswordValidationForm.vue"; import PasswordValidationForm from "./PasswordValidationForm.vue";
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
@ -23,7 +25,7 @@ const { mutate, isLoading } = useMutation({
}); });
}, },
onSuccess() { onSuccess() {
Toast.success("启用成功"); Toast.success(t("core.common.toast.enable_success"));
queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] }); queryClient.invalidateQueries({ queryKey: ["two-factor-settings"] });
modal.value?.close(); modal.value?.close();
}, },
@ -39,7 +41,7 @@ function onSubmit(password: string) {
ref="modal" ref="modal"
:width="500" :width="500"
:centered="false" :centered="false"
title="启用两步验证" :title="$t('core.uc_profile.2fa.operations.enable.title')"
@close="emit('close')" @close="emit('close')"
> >
<PasswordValidationForm @submit="onSubmit" /> <PasswordValidationForm @submit="onSubmit" />
@ -50,9 +52,11 @@ function onSubmit(password: string) {
type="secondary" type="secondary"
@click="$formkit.submit('password-validation-form')" @click="$formkit.submit('password-validation-form')"
> >
启用 {{ $t("core.common.buttons.enable") }}
</VButton>
<VButton @click="modal?.close()">
{{ $t("core.common.buttons.close") }}
</VButton> </VButton>
<VButton @click="modal?.close()"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>