mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-27 11:56:42 +08:00
fix(auth): repair TOTP CI failures
Address the migration and formatting failures reported on the TOTP branch. Migrations - Register totp_credentials and totp_recovery_codes in _ALEMBIC_TENANT_TABLES. On a legacy PostgreSQL install these two tables reference users.uuid, which only exists after 0009, so create_all() must not run ahead of Alembic the way it did for the other tenant tables. Without this the PostgreSQL migration test failed with "column uuid referenced in foreign key constraint does not exist". Tests - Resolve the Alembic head dynamically in the RAG document identity regression instead of pinning 0025_rag_document_identity. The TOTP and RAG branches now meet at a merge revision, so the pinned value was no longer the head. This matches the convention already used by test_migrations_postgres. Formatting - Apply ruff format to the new backend modules and prettier to the locale files and TOTP components, so the lint jobs pass.
This commit is contained in:
@@ -817,9 +817,7 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
rotate = bool(json_data.get('rotate', False))
|
rotate = bool(json_data.get('rotate', False))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
enrollment = await self.ap.totp_service.begin_enrollment(
|
enrollment = await self.ap.totp_service.begin_enrollment(account.uuid, account.user, rotate=rotate)
|
||||||
account.uuid, account.user, rotate=rotate
|
|
||||||
)
|
|
||||||
except totp_module.TotpError as e:
|
except totp_module.TotpError as e:
|
||||||
return self.http_status(409, e.code, str(e))
|
return self.http_status(409, e.code, str(e))
|
||||||
|
|
||||||
@@ -985,9 +983,7 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
return self.http_status(404, 'account_not_found', 'Account not found')
|
return self.http_status(404, 'account_not_found', 'Account not found')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
enrollment = await self.ap.totp_service.begin_enrollment(
|
enrollment = await self.ap.totp_service.begin_enrollment(target_account_uuid, target.user, force=True)
|
||||||
target_account_uuid, target.user, force=True
|
|
||||||
)
|
|
||||||
except totp_module.TotpError as e:
|
except totp_module.TotpError as e:
|
||||||
return self.http_status(409, e.code, str(e))
|
return self.http_status(409, e.code, str(e))
|
||||||
|
|
||||||
|
|||||||
@@ -348,9 +348,7 @@ class TotpService:
|
|||||||
pending_secret = self._decrypt_secret(credential.secret_ciphertext, credential.key_version)
|
pending_secret = self._decrypt_secret(credential.secret_ciphertext, credential.key_version)
|
||||||
return TotpEnrollment(
|
return TotpEnrollment(
|
||||||
uuid=credential.uuid,
|
uuid=credential.uuid,
|
||||||
qr_code_data_url=render_totp_qr_data_url(
|
qr_code_data_url=render_totp_qr_data_url(build_totp_uri(pending_secret.decode('ascii'), user_email)),
|
||||||
build_totp_uri(pending_secret.decode('ascii'), user_email)
|
|
||||||
),
|
|
||||||
algorithm=credential.algorithm,
|
algorithm=credential.algorithm,
|
||||||
digits=credential.digits,
|
digits=credential.digits,
|
||||||
period=credential.period,
|
period=credential.period,
|
||||||
@@ -688,9 +686,13 @@ class TotpService:
|
|||||||
return bool(result.rowcount)
|
return bool(result.rowcount)
|
||||||
|
|
||||||
async def count_unused_recovery_codes(self, account_uuid: str) -> int:
|
async def count_unused_recovery_codes(self, account_uuid: str) -> int:
|
||||||
statement = sqlalchemy.select(sqlalchemy.func.count()).select_from(totp_entity.TotpRecoveryCode).where(
|
statement = (
|
||||||
totp_entity.TotpRecoveryCode.account_uuid == account_uuid,
|
sqlalchemy.select(sqlalchemy.func.count())
|
||||||
totp_entity.TotpRecoveryCode.used_at.is_(None),
|
.select_from(totp_entity.TotpRecoveryCode)
|
||||||
|
.where(
|
||||||
|
totp_entity.TotpRecoveryCode.account_uuid == account_uuid,
|
||||||
|
totp_entity.TotpRecoveryCode.used_at.is_(None),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
async with self._session_factory()() as session:
|
async with self._session_factory()() as session:
|
||||||
return int(await session.scalar(statement) or 0)
|
return int(await session.scalar(statement) or 0)
|
||||||
|
|||||||
@@ -630,9 +630,7 @@ class UserService:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
verified = await totp_service.verify_code(
|
verified = await totp_service.verify_code(user_obj.uuid, code, allow_recovery=allow_recovery)
|
||||||
user_obj.uuid, code, allow_recovery=allow_recovery
|
|
||||||
)
|
|
||||||
except totp_service_module.TotpError:
|
except totp_service_module.TotpError:
|
||||||
# Covers "not enrolled" and "invalid code" alike. Both are simply a
|
# Covers "not enrolled" and "invalid code" alike. Both are simply a
|
||||||
# failed verification for this caller; neither should surface as a
|
# failed verification for this caller; neither should surface as a
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ _ALEMBIC_TENANT_TABLES = {
|
|||||||
'model_providers',
|
'model_providers',
|
||||||
'codex_credentials',
|
'codex_credentials',
|
||||||
'passkey_credentials',
|
'passkey_credentials',
|
||||||
|
'totp_credentials',
|
||||||
|
'totp_recovery_codes',
|
||||||
'llm_models',
|
'llm_models',
|
||||||
'embedding_models',
|
'embedding_models',
|
||||||
'rerank_models',
|
'rerank_models',
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ import pytest_asyncio
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
|
||||||
|
from alembic.config import Config as AlembicConfig
|
||||||
|
from alembic.script import ScriptDirectory
|
||||||
|
|
||||||
from langbot.pkg.api.http.context import ExecutionContext
|
from langbot.pkg.api.http.context import ExecutionContext
|
||||||
from langbot.pkg.entity.persistence.base import Base
|
from langbot.pkg.entity.persistence.base import Base
|
||||||
from langbot.pkg.entity.persistence.rag import File, KnowledgeBase
|
from langbot.pkg.entity.persistence.rag import File, KnowledgeBase
|
||||||
from langbot.pkg.entity.persistence.user import User
|
from langbot.pkg.entity.persistence.user import User
|
||||||
from langbot.pkg.entity.persistence.workspace import Workspace
|
from langbot.pkg.entity.persistence.workspace import Workspace
|
||||||
|
from langbot.pkg.persistence import alembic_runner
|
||||||
from langbot.pkg.persistence.alembic_runner import (
|
from langbot.pkg.persistence.alembic_runner import (
|
||||||
get_alembic_current,
|
get_alembic_current,
|
||||||
run_alembic_downgrade,
|
run_alembic_downgrade,
|
||||||
@@ -27,7 +31,21 @@ from langbot.pkg.rag.knowledge.kbmgr import RuntimeKnowledgeBase
|
|||||||
from langbot.pkg.workspace.errors import WorkspaceNotFoundError
|
from langbot.pkg.workspace.errors import WorkspaceNotFoundError
|
||||||
|
|
||||||
OLD_HEAD = '0024_passkey_credentials'
|
OLD_HEAD = '0024_passkey_credentials'
|
||||||
NEW_HEAD = '0025_rag_document_identity'
|
|
||||||
|
|
||||||
|
def _current_script_head() -> str:
|
||||||
|
"""Resolve the live Alembic head instead of pinning a revision number.
|
||||||
|
|
||||||
|
Parallel migrations (the TOTP and RAG document identity branches) are joined
|
||||||
|
by a merge revision, so the head moves whenever either branch gains a new
|
||||||
|
migration. Resolving it here keeps this test from needing an edit each time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cfg = AlembicConfig()
|
||||||
|
cfg.set_main_option('script_location', str(alembic_runner._ALEMBIC_DIR))
|
||||||
|
return ScriptDirectory.from_config(cfg).get_current_head()
|
||||||
|
|
||||||
|
|
||||||
CONTEXT = ExecutionContext(instance_uuid='instance-a', workspace_uuid='workspace-a', placement_generation=5)
|
CONTEXT = ExecutionContext(instance_uuid='instance-a', workspace_uuid='workspace-a', placement_generation=5)
|
||||||
|
|
||||||
|
|
||||||
@@ -361,7 +379,7 @@ async def test_populated_legacy_migration_roundtrip(database):
|
|||||||
row = (await conn.execute(sa.text('SELECT * FROM knowledge_base_files'))).mappings().one()
|
row = (await conn.execute(sa.text('SELECT * FROM knowledge_base_files'))).mappings().one()
|
||||||
assert row['uuid'] == 'legacy' and row['status'] == 'completed'
|
assert row['uuid'] == 'legacy' and row['status'] == 'completed'
|
||||||
assert row['engine_document_id'] is None
|
assert row['engine_document_id'] is None
|
||||||
assert await get_alembic_current(database) == NEW_HEAD
|
assert await get_alembic_current(database) == _current_script_head()
|
||||||
await run_alembic_upgrade(database)
|
await run_alembic_upgrade(database)
|
||||||
await run_alembic_stamp(database, OLD_HEAD)
|
await run_alembic_stamp(database, OLD_HEAD)
|
||||||
await run_alembic_upgrade(database)
|
await run_alembic_upgrade(database)
|
||||||
@@ -379,7 +397,7 @@ async def test_fresh_metadata_then_migration_is_idempotent(database):
|
|||||||
await create_schema(database)
|
await create_schema(database)
|
||||||
await run_alembic_stamp(database, OLD_HEAD)
|
await run_alembic_stamp(database, OLD_HEAD)
|
||||||
await run_alembic_upgrade(database)
|
await run_alembic_upgrade(database)
|
||||||
assert await get_alembic_current(database) == NEW_HEAD
|
assert await get_alembic_current(database) == _current_script_head()
|
||||||
async with database.connect() as conn:
|
async with database.connect() as conn:
|
||||||
assert 'engine_document_id' in await conn.run_sync(
|
assert 'engine_document_id' in await conn.run_sync(
|
||||||
lambda sync: {col['name'] for col in sa.inspect(sync).get_columns('knowledge_base_files')}
|
lambda sync: {col['name'] for col in sa.inspect(sync).get_columns('knowledge_base_files')}
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ export default function AccountSettingsPanel({
|
|||||||
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
||||||
const [totpDialogOpen, setTotpDialogOpen] = useState(false);
|
const [totpDialogOpen, setTotpDialogOpen] = useState(false);
|
||||||
// Latched when the dialog opens so a status refresh cannot swap the flow.
|
// Latched when the dialog opens so a status refresh cannot swap the flow.
|
||||||
const [totpDialogMode, setTotpDialogMode] = useState<TotpDialogMode>('enroll');
|
const [totpDialogMode, setTotpDialogMode] =
|
||||||
|
useState<TotpDialogMode>('enroll');
|
||||||
// Owner/admin re-binding flow: the target Account is latched on open.
|
// Owner/admin re-binding flow: the target Account is latched on open.
|
||||||
const [adminResetOpen, setAdminResetOpen] = useState(false);
|
const [adminResetOpen, setAdminResetOpen] = useState(false);
|
||||||
const [adminResetTarget, setAdminResetTarget] = useState<TotpAccountRow | null>(
|
const [adminResetTarget, setAdminResetTarget] =
|
||||||
null,
|
useState<TotpAccountRow | null>(null);
|
||||||
);
|
|
||||||
const [totpRows, setTotpRows] = useState<TotpAccountRow[]>([]);
|
const [totpRows, setTotpRows] = useState<TotpAccountRow[]>([]);
|
||||||
const [isManager, setIsManager] = useState(false);
|
const [isManager, setIsManager] = useState(false);
|
||||||
const [accountUuid, setAccountUuid] = useState('');
|
const [accountUuid, setAccountUuid] = useState('');
|
||||||
@@ -497,7 +497,9 @@ export default function AccountSettingsPanel({
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 cursor-pointer"
|
className="h-8 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTotpDialogMode(row.enabled ? 'manage' : 'enroll');
|
setTotpDialogMode(
|
||||||
|
row.enabled ? 'manage' : 'enroll',
|
||||||
|
);
|
||||||
setTotpDialogOpen(true);
|
setTotpDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
disabled={!systemInfo.allow_modify_login_info}
|
disabled={!systemInfo.allow_modify_login_info}
|
||||||
|
|||||||
@@ -196,7 +196,10 @@ export default function TotpEnrollDialog({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}
|
||||||
|
>
|
||||||
<DialogContent className="sm:max-w-[460px]">
|
<DialogContent className="sm:max-w-[460px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1309,7 +1309,9 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public confirmTotpEnroll(code: string): Promise<{ recovery_codes: string[] }> {
|
public confirmTotpEnroll(
|
||||||
|
code: string,
|
||||||
|
): Promise<{ recovery_codes: string[] }> {
|
||||||
return this.post(
|
return this.post(
|
||||||
'/api/v1/user/totp/enroll/confirm',
|
'/api/v1/user/totp/enroll/confirm',
|
||||||
{ code },
|
{ code },
|
||||||
@@ -1328,9 +1330,13 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public disableTotp(code: string): Promise<void> {
|
public disableTotp(code: string): Promise<void> {
|
||||||
return this.post('/api/v1/user/totp/disable', { code }, {
|
return this.post(
|
||||||
skipWorkspace: true,
|
'/api/v1/user/totp/disable',
|
||||||
});
|
{ code },
|
||||||
|
{
|
||||||
|
skipWorkspace: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ TOTP oversight (Workspace owner/admin only) ============
|
// ============ TOTP oversight (Workspace owner/admin only) ============
|
||||||
|
|||||||
+168
-162
@@ -234,24 +234,30 @@ export default function Login() {
|
|||||||
toast.success(t('common.loginSuccess'));
|
toast.success(t('common.loginSuccess'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: { code?: string; msg?: string; data?: { challenge_token?: string } }) => {
|
.catch(
|
||||||
// The backend answers `totp_required` when the password was correct but
|
(error: {
|
||||||
// a second factor is still outstanding. It also hands back the
|
code?: string;
|
||||||
// challenge token that must accompany the code.
|
msg?: string;
|
||||||
if (error?.code === 'totp_required') {
|
data?: { challenge_token?: string };
|
||||||
setPendingEmail(username);
|
}) => {
|
||||||
setTotpChallengeToken(error?.data?.challenge_token || '');
|
// The backend answers `totp_required` when the password was correct but
|
||||||
setTotpStep(true);
|
// a second factor is still outstanding. It also hands back the
|
||||||
setUseRecoveryCode(false);
|
// challenge token that must accompany the code.
|
||||||
setTotpCode('');
|
if (error?.code === 'totp_required') {
|
||||||
return;
|
setPendingEmail(username);
|
||||||
}
|
setTotpChallengeToken(error?.data?.challenge_token || '');
|
||||||
if (error?.code === 'totp_invalid_code') {
|
setTotpStep(true);
|
||||||
toast.error(t('common.totpInvalidCode'));
|
setUseRecoveryCode(false);
|
||||||
return;
|
setTotpCode('');
|
||||||
}
|
return;
|
||||||
toast.error(t('common.loginFailed'));
|
}
|
||||||
});
|
if (error?.code === 'totp_invalid_code') {
|
||||||
|
toast.error(t('common.totpInvalidCode'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.error(t('common.loginFailed'));
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTotpSubmit(event: React.FormEvent) {
|
async function handleTotpSubmit(event: React.FormEvent) {
|
||||||
@@ -477,157 +483,157 @@ export default function Login() {
|
|||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Space and password login are per-account capabilities. */}
|
{/* Space and password login are per-account capabilities. */}
|
||||||
{showSpaceLogin && (
|
{showSpaceLogin && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
onClick={handleSpaceLoginClick}
|
onClick={handleSpaceLoginClick}
|
||||||
disabled={spaceLoading}
|
disabled={spaceLoading}
|
||||||
>
|
>
|
||||||
{spaceLoading ? (
|
{spaceLoading ? (
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Layers className="mr-2 h-4 w-4" />
|
<Layers className="mr-2 h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
{t('common.loginWithSpace')}
|
{t('common.loginWithSpace')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showPasskeyLogin && (
|
{showPasskeyLogin && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
onClick={handlePasskeyLogin}
|
onClick={handlePasskeyLogin}
|
||||||
disabled={passkeyLoading}
|
disabled={passkeyLoading}
|
||||||
>
|
>
|
||||||
{passkeyLoading ? (
|
{passkeyLoading ? (
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Fingerprint className="mr-2 h-4 w-4" />
|
<Fingerprint className="mr-2 h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
{t('common.loginWithPasskey')}
|
{t('common.loginWithPasskey')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Divider - only show if both login methods are available */}
|
{/* Divider - only show if both login methods are available */}
|
||||||
{(showSpaceLogin || showPasskeyLogin) && showLocalLogin && (
|
{(showSpaceLogin || showPasskeyLogin) && showLocalLogin && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-0 flex items-center">
|
<div className="absolute inset-0 flex items-center">
|
||||||
<span className="w-full border-t" />
|
<span className="w-full border-t" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center text-xs uppercase">
|
<div className="relative flex justify-center text-xs uppercase">
|
||||||
<span className="bg-white dark:bg-card px-2 text-muted-foreground">
|
<span className="bg-white dark:bg-card px-2 text-muted-foreground">
|
||||||
{t('common.or')}
|
{t('common.or')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Password login remains available to every account with a password. */}
|
{/* Password login remains available to every account with a password. */}
|
||||||
{showLocalLogin && (
|
{showLocalLogin && (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('common.email')}</FormLabel>
|
<FormLabel>{t('common.email')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Mail className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
<Mail className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('common.enterEmail')}
|
placeholder={t('common.enterEmail')}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<FormLabel>{t('common.password')}</FormLabel>
|
<FormLabel>{t('common.password')}</FormLabel>
|
||||||
<Link
|
<Link
|
||||||
to="/reset-password"
|
to="/reset-password"
|
||||||
className="text-sm text-blue-500"
|
className="text-sm text-blue-500"
|
||||||
>
|
>
|
||||||
{t('common.forgotPassword')}
|
{t('common.forgotPassword')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Lock className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
<Lock className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={t('common.enterPassword')}
|
placeholder={t('common.enterPassword')}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant={showSpaceLogin ? 'outline' : 'default'}
|
variant={showSpaceLogin ? 'outline' : 'default'}
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
|
>
|
||||||
|
{t('common.loginWithPassword')}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-xs text-center text-muted-foreground">
|
||||||
|
{t('common.agreementNotice')}{' '}
|
||||||
|
<a
|
||||||
|
href="https://langbot.app/terms"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{t('common.loginWithPassword')}
|
{t('common.termsOfService')}
|
||||||
</Button>
|
</a>
|
||||||
</form>
|
{'、'}
|
||||||
</Form>
|
<a
|
||||||
)}
|
href="https://langbot.app/privacy"
|
||||||
|
target="_blank"
|
||||||
<p className="text-xs text-center text-muted-foreground">
|
rel="noopener noreferrer"
|
||||||
{t('common.agreementNotice')}{' '}
|
className="underline hover:text-foreground transition-colors"
|
||||||
<a
|
>
|
||||||
href="https://langbot.app/terms"
|
{t('common.privacyPolicy')}
|
||||||
target="_blank"
|
</a>{' '}
|
||||||
rel="noopener noreferrer"
|
{t('common.and')}{' '}
|
||||||
className="underline hover:text-foreground transition-colors"
|
<a
|
||||||
>
|
href={t('common.dataCollectionPolicyUrl')}
|
||||||
{t('common.termsOfService')}
|
target="_blank"
|
||||||
</a>
|
rel="noopener noreferrer"
|
||||||
{'、'}
|
className="underline hover:text-foreground transition-colors"
|
||||||
<a
|
>
|
||||||
href="https://langbot.app/privacy"
|
{t('common.dataCollectionPolicy')}
|
||||||
target="_blank"
|
</a>
|
||||||
rel="noopener noreferrer"
|
</p>
|
||||||
className="underline hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
{t('common.privacyPolicy')}
|
|
||||||
</a>{' '}
|
|
||||||
{t('common.and')}{' '}
|
|
||||||
<a
|
|
||||||
href={t('common.dataCollectionPolicyUrl')}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
{t('common.dataCollectionPolicy')}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -121,7 +121,11 @@ export default function ResetPassword() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodButton = (value: ResetMethod, label: string, Icon: typeof KeyRound) => (
|
const methodButton = (
|
||||||
|
value: ResetMethod,
|
||||||
|
label: string,
|
||||||
|
Icon: typeof KeyRound,
|
||||||
|
) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -264,7 +268,9 @@ export default function ResetPassword() {
|
|||||||
: t('resetPassword.enterRecoveryCodeValue')
|
: t('resetPassword.enterRecoveryCodeValue')
|
||||||
}
|
}
|
||||||
className={`pl-10 ${
|
className={`pl-10 ${
|
||||||
method === 'totp' ? 'tracking-widest' : 'font-mono'
|
method === 'totp'
|
||||||
|
? 'tracking-widest'
|
||||||
|
: 'font-mono'
|
||||||
}`}
|
}`}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
|||||||
@@ -1406,9 +1406,11 @@ const enUS = {
|
|||||||
totpStartEnroll: 'Generate Secret',
|
totpStartEnroll: 'Generate Secret',
|
||||||
totpGeneratingSecret: 'Generating a new secret...',
|
totpGeneratingSecret: 'Generating a new secret...',
|
||||||
totpManageTitle: 'Two-factor authentication',
|
totpManageTitle: 'Two-factor authentication',
|
||||||
totpManageDesc: 'Regenerate your recovery codes or turn the second factor off.',
|
totpManageDesc:
|
||||||
|
'Regenerate your recovery codes or turn the second factor off.',
|
||||||
totpRegenerateCodes: 'Regenerate recovery codes',
|
totpRegenerateCodes: 'Regenerate recovery codes',
|
||||||
totpRegenerateDesc: 'Enter a current authenticator or recovery code to issue a fresh set of codes.',
|
totpRegenerateDesc:
|
||||||
|
'Enter a current authenticator or recovery code to issue a fresh set of codes.',
|
||||||
totpRecoveryCodesRegenerated: 'New recovery codes generated',
|
totpRecoveryCodesRegenerated: 'New recovery codes generated',
|
||||||
totpStatusDisabled: 'Not enabled',
|
totpStatusDisabled: 'Not enabled',
|
||||||
totpCodesRemaining: '{{count}} recovery codes left',
|
totpCodesRemaining: '{{count}} recovery codes left',
|
||||||
@@ -1416,11 +1418,16 @@ const enUS = {
|
|||||||
'Owners and admins can review and revoke the second factor of any Account.',
|
'Owners and admins can review and revoke the second factor of any Account.',
|
||||||
revokeTotp: 'Re-bind',
|
revokeTotp: 'Re-bind',
|
||||||
totpAdminResetTitle: 'Re-bind two-factor authentication for {{user}}',
|
totpAdminResetTitle: 'Re-bind two-factor authentication for {{user}}',
|
||||||
totpAdminResetDesc: 'Have the Account scan the QR code with their authenticator, then enter the 6-digit code below to finish binding.',
|
totpAdminResetDesc:
|
||||||
totpAdminResetWarning: 'Once re-binding starts, {{user}} existing authenticator stops working immediately.',
|
'Have the Account scan the QR code with their authenticator, then enter the 6-digit code below to finish binding.',
|
||||||
totpAdminResetHint: 'If the Account cannot sign in right now, they can scan this QR code in any authenticator app.',
|
totpAdminResetWarning:
|
||||||
totpAdminHandOverCodes: 'Hand these recovery codes over to {{user}}. They are shown only once.',
|
'Once re-binding starts, {{user}} existing authenticator stops working immediately.',
|
||||||
revokeTotpConfirm: 'Turn off two-factor authentication for {{user}}? They will be able to sign in with only a password afterwards.',
|
totpAdminResetHint:
|
||||||
|
'If the Account cannot sign in right now, they can scan this QR code in any authenticator app.',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'Hand these recovery codes over to {{user}}. They are shown only once.',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'Turn off two-factor authentication for {{user}}? They will be able to sign in with only a password afterwards.',
|
||||||
revokeTotpSuccess: 'Two-factor authentication revoked',
|
revokeTotpSuccess: 'Two-factor authentication revoked',
|
||||||
you: 'you',
|
you: 'you',
|
||||||
noAccounts: 'No accounts to show',
|
noAccounts: 'No accounts to show',
|
||||||
|
|||||||
@@ -99,8 +99,10 @@ const esES = {
|
|||||||
verify: 'Verificar',
|
verify: 'Verificar',
|
||||||
back: 'Atrás',
|
back: 'Atrás',
|
||||||
totpChallengeTitle: 'Verificación en dos pasos',
|
totpChallengeTitle: 'Verificación en dos pasos',
|
||||||
totpChallengeDesc: 'Introduce el código de 6 dígitos de tu aplicación de autenticación para continuar',
|
totpChallengeDesc:
|
||||||
totpUseRecoveryCode: 'Introduce uno de tus códigos de recuperación de un solo uso para continuar',
|
'Introduce el código de 6 dígitos de tu aplicación de autenticación para continuar',
|
||||||
|
totpUseRecoveryCode:
|
||||||
|
'Introduce uno de tus códigos de recuperación de un solo uso para continuar',
|
||||||
enterTotpCode: 'Introduce el código de 6 dígitos',
|
enterTotpCode: 'Introduce el código de 6 dígitos',
|
||||||
enterRecoveryCode: 'Introduce el código de recuperación',
|
enterRecoveryCode: 'Introduce el código de recuperación',
|
||||||
useRecoveryCode: 'Usar un código de recuperación',
|
useRecoveryCode: 'Usar un código de recuperación',
|
||||||
@@ -1353,17 +1355,20 @@ const esES = {
|
|||||||
resetFailed:
|
resetFailed:
|
||||||
'Error al restablecer la contraseña, por favor verifica tu correo y clave de recuperación',
|
'Error al restablecer la contraseña, por favor verifica tu correo y clave de recuperación',
|
||||||
backToLogin: 'Volver al inicio de sesión',
|
backToLogin: 'Volver al inicio de sesión',
|
||||||
secondFactorFailed: 'La verificación falló, comprueba el código e inténtalo de nuevo',
|
secondFactorFailed:
|
||||||
|
'La verificación falló, comprueba el código e inténtalo de nuevo',
|
||||||
verifyWith: 'Verificar con',
|
verifyWith: 'Verificar con',
|
||||||
methodRecoveryKey: 'Clave de recuperación',
|
methodRecoveryKey: 'Clave de recuperación',
|
||||||
methodTotp: 'Autenticador',
|
methodTotp: 'Autenticador',
|
||||||
methodRecoveryCode: 'Código de recuperación',
|
methodRecoveryCode: 'Código de recuperación',
|
||||||
totpCode: 'Código del autenticador',
|
totpCode: 'Código del autenticador',
|
||||||
totpCodeDescription: 'Introduce el código de 6 dígitos que muestra tu aplicación de autenticación',
|
totpCodeDescription:
|
||||||
|
'Introduce el código de 6 dígitos que muestra tu aplicación de autenticación',
|
||||||
totpCodeRequired: 'El código del autenticador no puede estar vacío',
|
totpCodeRequired: 'El código del autenticador no puede estar vacío',
|
||||||
enterTotpCode: 'Introduce el código de 6 dígitos',
|
enterTotpCode: 'Introduce el código de 6 dígitos',
|
||||||
recoveryCode: 'Código de recuperación',
|
recoveryCode: 'Código de recuperación',
|
||||||
recoveryCodeDescription: 'Introduce uno de los códigos de recuperación de un solo uso que guardaste al activar la verificación en dos pasos',
|
recoveryCodeDescription:
|
||||||
|
'Introduce uno de los códigos de recuperación de un solo uso que guardaste al activar la verificación en dos pasos',
|
||||||
recoveryCodeRequired: 'El código de recuperación no puede estar vacío',
|
recoveryCodeRequired: 'El código de recuperación no puede estar vacío',
|
||||||
enterRecoveryCodeValue: 'Introduce el código de recuperación',
|
enterRecoveryCodeValue: 'Introduce el código de recuperación',
|
||||||
},
|
},
|
||||||
@@ -1427,29 +1432,40 @@ const esES = {
|
|||||||
passkeyDeleteSuccess: 'Llave de acceso eliminada',
|
passkeyDeleteSuccess: 'Llave de acceso eliminada',
|
||||||
passkeyRenameSuccess: 'Nombre de llave de acceso modificado con éxito',
|
passkeyRenameSuccess: 'Nombre de llave de acceso modificado con éxito',
|
||||||
totpSectionTitle: 'Verificación en dos pasos',
|
totpSectionTitle: 'Verificación en dos pasos',
|
||||||
totpSectionDesc: 'Añade una contraseña de un solo uso basada en tiempo como segundo factor de inicio de sesión',
|
totpSectionDesc:
|
||||||
totpEnabledDesc: 'Verificación en dos pasos activada · quedan {{count}} códigos de recuperación',
|
'Añade una contraseña de un solo uso basada en tiempo como segundo factor de inicio de sesión',
|
||||||
|
totpEnabledDesc:
|
||||||
|
'Verificación en dos pasos activada · quedan {{count}} códigos de recuperación',
|
||||||
enableTotp: 'Activar',
|
enableTotp: 'Activar',
|
||||||
manageTotp: 'Gestionar',
|
manageTotp: 'Gestionar',
|
||||||
totpEnrollTitle: 'Activar la verificación en dos pasos',
|
totpEnrollTitle: 'Activar la verificación en dos pasos',
|
||||||
totpEnrollDesc: 'Escanea el código QR con tu aplicación de autenticación y confirma el código generado',
|
totpEnrollDesc:
|
||||||
|
'Escanea el código QR con tu aplicación de autenticación y confirma el código generado',
|
||||||
totpStartEnroll: 'Generar secreto',
|
totpStartEnroll: 'Generar secreto',
|
||||||
totpGeneratingSecret: 'Generando un nuevo secreto…',
|
totpGeneratingSecret: 'Generando un nuevo secreto…',
|
||||||
totpManageTitle: 'Verificación en dos pasos',
|
totpManageTitle: 'Verificación en dos pasos',
|
||||||
totpManageDesc: 'Regenera tus códigos de recuperación o desactiva el segundo factor.',
|
totpManageDesc:
|
||||||
|
'Regenera tus códigos de recuperación o desactiva el segundo factor.',
|
||||||
totpRegenerateCodes: 'Regenerar códigos de recuperación',
|
totpRegenerateCodes: 'Regenerar códigos de recuperación',
|
||||||
totpRegenerateDesc: 'Introduce un código actual del autenticador o de recuperación para emitir un conjunto nuevo.',
|
totpRegenerateDesc:
|
||||||
|
'Introduce un código actual del autenticador o de recuperación para emitir un conjunto nuevo.',
|
||||||
totpRecoveryCodesRegenerated: 'Nuevos códigos de recuperación generados',
|
totpRecoveryCodesRegenerated: 'Nuevos códigos de recuperación generados',
|
||||||
totpStatusDisabled: 'No activada',
|
totpStatusDisabled: 'No activada',
|
||||||
totpCodesRemaining: 'Quedan {{count}} códigos de recuperación',
|
totpCodesRemaining: 'Quedan {{count}} códigos de recuperación',
|
||||||
totpManagerSectionDesc: 'Los propietarios y administradores pueden revisar y restablecer el segundo factor de cualquier cuenta.',
|
totpManagerSectionDesc:
|
||||||
|
'Los propietarios y administradores pueden revisar y restablecer el segundo factor de cualquier cuenta.',
|
||||||
revokeTotp: 'Reasignar',
|
revokeTotp: 'Reasignar',
|
||||||
totpAdminResetTitle: 'Reasignar la verificación en dos pasos de {{user}}',
|
totpAdminResetTitle: 'Reasignar la verificación en dos pasos de {{user}}',
|
||||||
totpAdminResetDesc: 'Pide a la cuenta que escanee el código QR con su autenticador y que introduzca abajo el código de 6 dígitos para finalizar.',
|
totpAdminResetDesc:
|
||||||
totpAdminResetWarning: 'Al iniciar la reasignación, el autenticador actual de {{user}} deja de funcionar de inmediato.',
|
'Pide a la cuenta que escanee el código QR con su autenticador y que introduzca abajo el código de 6 dígitos para finalizar.',
|
||||||
totpAdminResetHint: 'Si la cuenta no puede iniciar sesión ahora, puede escanear este código QR en cualquier aplicación de autenticación.',
|
totpAdminResetWarning:
|
||||||
totpAdminHandOverCodes: 'Entrega estos códigos de recuperación a {{user}}. Solo se muestran una vez.',
|
'Al iniciar la reasignación, el autenticador actual de {{user}} deja de funcionar de inmediato.',
|
||||||
revokeTotpConfirm: '¿Desactivar la verificación en dos pasos de {{user}}? Después podrá iniciar sesión solo con la contraseña.',
|
totpAdminResetHint:
|
||||||
|
'Si la cuenta no puede iniciar sesión ahora, puede escanear este código QR en cualquier aplicación de autenticación.',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'Entrega estos códigos de recuperación a {{user}}. Solo se muestran una vez.',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'¿Desactivar la verificación en dos pasos de {{user}}? Después podrá iniciar sesión solo con la contraseña.',
|
||||||
revokeTotpSuccess: 'Verificación en dos pasos restablecida',
|
revokeTotpSuccess: 'Verificación en dos pasos restablecida',
|
||||||
you: 'tú',
|
you: 'tú',
|
||||||
noAccounts: 'No hay cuentas que mostrar',
|
noAccounts: 'No hay cuentas que mostrar',
|
||||||
@@ -1463,13 +1479,16 @@ const esES = {
|
|||||||
totpLastUsed: 'Última verificación: {{date}}',
|
totpLastUsed: 'Última verificación: {{date}}',
|
||||||
totpNeverUsed: 'Aún no usado',
|
totpNeverUsed: 'Aún no usado',
|
||||||
disableTotp: 'Desactivar la verificación en dos pasos',
|
disableTotp: 'Desactivar la verificación en dos pasos',
|
||||||
disableTotpDesc: 'Introduce un código actual del autenticador o un código de recuperación para desactivar la verificación en dos pasos',
|
disableTotpDesc:
|
||||||
|
'Introduce un código actual del autenticador o un código de recuperación para desactivar la verificación en dos pasos',
|
||||||
totpEnabledSuccess: 'Verificación en dos pasos activada',
|
totpEnabledSuccess: 'Verificación en dos pasos activada',
|
||||||
totpDisabledSuccess: 'Verificación en dos pasos desactivada',
|
totpDisabledSuccess: 'Verificación en dos pasos desactivada',
|
||||||
totpInvalidCode: 'Código no válido, compruébalo e inténtalo de nuevo',
|
totpInvalidCode: 'Código no válido, compruébalo e inténtalo de nuevo',
|
||||||
totpRecoveryCodesTitle: 'Códigos de recuperación',
|
totpRecoveryCodesTitle: 'Códigos de recuperación',
|
||||||
totpRecoveryCodesDesc: 'Guarda estos códigos de recuperación de un solo uso en un lugar seguro. Solo se muestran una vez.',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: 'Cada código funciona una sola vez. Si pierdes el autenticador y estos códigos, perderás el acceso al inicio de sesión.',
|
'Guarda estos códigos de recuperación de un solo uso en un lugar seguro. Solo se muestran una vez.',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'Cada código funciona una sola vez. Si pierdes el autenticador y estos códigos, perderás el acceso al inicio de sesión.',
|
||||||
totpSavedCodes: 'He guardado estos códigos',
|
totpSavedCodes: 'He guardado estos códigos',
|
||||||
regenerateRecoveryCodes: 'Regenerar códigos de recuperación',
|
regenerateRecoveryCodes: 'Regenerar códigos de recuperación',
|
||||||
bindSpaceFailed: 'Error al vincular la cuenta de LangBot',
|
bindSpaceFailed: 'Error al vincular la cuenta de LangBot',
|
||||||
|
|||||||
@@ -1323,7 +1323,8 @@ const jaJP = {
|
|||||||
resetFailed:
|
resetFailed:
|
||||||
'パスワードのリセットに失敗しました。メールアドレスと復旧キーを確認してください',
|
'パスワードのリセットに失敗しました。メールアドレスと復旧キーを確認してください',
|
||||||
backToLogin: 'ログインに戻る',
|
backToLogin: 'ログインに戻る',
|
||||||
secondFactorFailed: '確認に失敗しました。コードを確認して再試行してください',
|
secondFactorFailed:
|
||||||
|
'確認に失敗しました。コードを確認して再試行してください',
|
||||||
verifyWith: '確認方法',
|
verifyWith: '確認方法',
|
||||||
methodRecoveryKey: 'リカバリーキー',
|
methodRecoveryKey: 'リカバリーキー',
|
||||||
methodTotp: '認証アプリ',
|
methodTotp: '認証アプリ',
|
||||||
@@ -1333,7 +1334,8 @@ const jaJP = {
|
|||||||
totpCodeRequired: '認証コードを入力してください',
|
totpCodeRequired: '認証コードを入力してください',
|
||||||
enterTotpCode: '6桁のコードを入力',
|
enterTotpCode: '6桁のコードを入力',
|
||||||
recoveryCode: 'リカバリーコード',
|
recoveryCode: 'リカバリーコード',
|
||||||
recoveryCodeDescription: '二段階認証を有効にしたときに保存した一度限りのリカバリーコードを入力してください',
|
recoveryCodeDescription:
|
||||||
|
'二段階認証を有効にしたときに保存した一度限りのリカバリーコードを入力してください',
|
||||||
recoveryCodeRequired: 'リカバリーコードを入力してください',
|
recoveryCodeRequired: 'リカバリーコードを入力してください',
|
||||||
enterRecoveryCodeValue: 'リカバリーコードを入力',
|
enterRecoveryCodeValue: 'リカバリーコードを入力',
|
||||||
},
|
},
|
||||||
@@ -1396,29 +1398,39 @@ const jaJP = {
|
|||||||
passkeyDeleteSuccess: 'パスキーを削除しました',
|
passkeyDeleteSuccess: 'パスキーを削除しました',
|
||||||
passkeyRenameSuccess: 'パスキー名を変更しました',
|
passkeyRenameSuccess: 'パスキー名を変更しました',
|
||||||
totpSectionTitle: '二段階認証',
|
totpSectionTitle: '二段階認証',
|
||||||
totpSectionDesc: 'ログインの第二要素として時間ベースのワンタイムパスワードを追加します',
|
totpSectionDesc:
|
||||||
|
'ログインの第二要素として時間ベースのワンタイムパスワードを追加します',
|
||||||
totpEnabledDesc: '二段階認証は有効です · 残りリカバリーコード {{count}} 個',
|
totpEnabledDesc: '二段階認証は有効です · 残りリカバリーコード {{count}} 個',
|
||||||
enableTotp: '有効化',
|
enableTotp: '有効化',
|
||||||
manageTotp: '管理',
|
manageTotp: '管理',
|
||||||
totpEnrollTitle: '二段階認証を有効にする',
|
totpEnrollTitle: '二段階認証を有効にする',
|
||||||
totpEnrollDesc: '認証アプリでQRコードをスキャンし、生成されたコードを入力して確認します',
|
totpEnrollDesc:
|
||||||
|
'認証アプリでQRコードをスキャンし、生成されたコードを入力して確認します',
|
||||||
totpStartEnroll: 'シークレットを生成',
|
totpStartEnroll: 'シークレットを生成',
|
||||||
totpGeneratingSecret: '新しいシークレットを生成しています…',
|
totpGeneratingSecret: '新しいシークレットを生成しています…',
|
||||||
totpManageTitle: '二段階認証',
|
totpManageTitle: '二段階認証',
|
||||||
totpManageDesc: 'リカバリーコードを再生成するか、二段階認証を無効にできます。',
|
totpManageDesc:
|
||||||
|
'リカバリーコードを再生成するか、二段階認証を無効にできます。',
|
||||||
totpRegenerateCodes: 'リカバリーコードを再生成',
|
totpRegenerateCodes: 'リカバリーコードを再生成',
|
||||||
totpRegenerateDesc: '現在の認証コードまたはリカバリーコードを入力すると、新しいコードを発行します。',
|
totpRegenerateDesc:
|
||||||
|
'現在の認証コードまたはリカバリーコードを入力すると、新しいコードを発行します。',
|
||||||
totpRecoveryCodesRegenerated: '新しいリカバリーコードを生成しました',
|
totpRecoveryCodesRegenerated: '新しいリカバリーコードを生成しました',
|
||||||
totpStatusDisabled: '未設定',
|
totpStatusDisabled: '未設定',
|
||||||
totpCodesRemaining: 'リカバリーコード残り {{count}} 個',
|
totpCodesRemaining: 'リカバリーコード残り {{count}} 個',
|
||||||
totpManagerSectionDesc: 'オーナーと管理者はすべてのアカウントの二段階認証を確認・解除できます。',
|
totpManagerSectionDesc:
|
||||||
|
'オーナーと管理者はすべてのアカウントの二段階認証を確認・解除できます。',
|
||||||
revokeTotp: '再バインド',
|
revokeTotp: '再バインド',
|
||||||
totpAdminResetTitle: '{{user}} の二段階認証を再バインド',
|
totpAdminResetTitle: '{{user}} の二段階認証を再バインド',
|
||||||
totpAdminResetDesc: '対象アカウントに認証アプリでQRコードを読み取ってもらい、表示される6桁のコードを下に入力して完了します。',
|
totpAdminResetDesc:
|
||||||
totpAdminResetWarning: '再バインドを開始すると、{{user}} の既存の認証アプリは直ちに無効になります。',
|
'対象アカウントに認証アプリでQRコードを読み取ってもらい、表示される6桁のコードを下に入力して完了します。',
|
||||||
totpAdminResetHint: '対象アカウントが今ログインできない場合、任意の認証アプリでこのQRコードを読み取ってもらえます。',
|
totpAdminResetWarning:
|
||||||
totpAdminHandOverCodes: 'これらのリカバリーコードを {{user}} に渡してください。表示は一度だけです。',
|
'再バインドを開始すると、{{user}} の既存の認証アプリは直ちに無効になります。',
|
||||||
revokeTotpConfirm: '{{user}} の二段階認証を無効にしますか?以降はパスワードのみでログインできます。',
|
totpAdminResetHint:
|
||||||
|
'対象アカウントが今ログインできない場合、任意の認証アプリでこのQRコードを読み取ってもらえます。',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'これらのリカバリーコードを {{user}} に渡してください。表示は一度だけです。',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'{{user}} の二段階認証を無効にしますか?以降はパスワードのみでログインできます。',
|
||||||
revokeTotpSuccess: '二段階認証を解除しました',
|
revokeTotpSuccess: '二段階認証を解除しました',
|
||||||
you: '自分',
|
you: '自分',
|
||||||
noAccounts: '表示するアカウントがありません',
|
noAccounts: '表示するアカウントがありません',
|
||||||
@@ -1432,13 +1444,16 @@ const jaJP = {
|
|||||||
totpLastUsed: '最終確認: {{date}}',
|
totpLastUsed: '最終確認: {{date}}',
|
||||||
totpNeverUsed: '未使用',
|
totpNeverUsed: '未使用',
|
||||||
disableTotp: '二段階認証を無効にする',
|
disableTotp: '二段階認証を無効にする',
|
||||||
disableTotpDesc: '現在の認証コードまたはリカバリーコードを入力して、二段階認証を無効にします',
|
disableTotpDesc:
|
||||||
|
'現在の認証コードまたはリカバリーコードを入力して、二段階認証を無効にします',
|
||||||
totpEnabledSuccess: '二段階認証を有効にしました',
|
totpEnabledSuccess: '二段階認証を有効にしました',
|
||||||
totpDisabledSuccess: '二段階認証を無効にしました',
|
totpDisabledSuccess: '二段階認証を無効にしました',
|
||||||
totpInvalidCode: 'コードが無効です。確認してもう一度お試しください',
|
totpInvalidCode: 'コードが無効です。確認してもう一度お試しください',
|
||||||
totpRecoveryCodesTitle: 'リカバリーコード',
|
totpRecoveryCodesTitle: 'リカバリーコード',
|
||||||
totpRecoveryCodesDesc: 'これらの一度限りのリカバリーコードを安全な場所に保存してください。表示は一度だけです。',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: '各コードは一度だけ使用できます。認証アプリとこれらのコードを失うと、ログインできなくなります。',
|
'これらの一度限りのリカバリーコードを安全な場所に保存してください。表示は一度だけです。',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'各コードは一度だけ使用できます。認証アプリとこれらのコードを失うと、ログインできなくなります。',
|
||||||
totpSavedCodes: 'コードを保存しました',
|
totpSavedCodes: 'コードを保存しました',
|
||||||
regenerateRecoveryCodes: 'リカバリーコードを再生成',
|
regenerateRecoveryCodes: 'リカバリーコードを再生成',
|
||||||
bindSpaceFailed: 'LangBot アカウントの連携に失敗しました',
|
bindSpaceFailed: 'LangBot アカウントの連携に失敗しました',
|
||||||
|
|||||||
@@ -96,8 +96,10 @@ const ruRU = {
|
|||||||
verify: 'Подтвердить',
|
verify: 'Подтвердить',
|
||||||
back: 'Назад',
|
back: 'Назад',
|
||||||
totpChallengeTitle: 'Двухфакторная проверка',
|
totpChallengeTitle: 'Двухфакторная проверка',
|
||||||
totpChallengeDesc: 'Введите 6-значный код из приложения-аутентификатора, чтобы продолжить',
|
totpChallengeDesc:
|
||||||
totpUseRecoveryCode: 'Введите один из одноразовых кодов восстановления, чтобы продолжить',
|
'Введите 6-значный код из приложения-аутентификатора, чтобы продолжить',
|
||||||
|
totpUseRecoveryCode:
|
||||||
|
'Введите один из одноразовых кодов восстановления, чтобы продолжить',
|
||||||
enterTotpCode: 'Введите 6-значный код',
|
enterTotpCode: 'Введите 6-значный код',
|
||||||
enterRecoveryCode: 'Введите код восстановления',
|
enterRecoveryCode: 'Введите код восстановления',
|
||||||
useRecoveryCode: 'Использовать код восстановления',
|
useRecoveryCode: 'Использовать код восстановления',
|
||||||
@@ -1337,7 +1339,8 @@ const ruRU = {
|
|||||||
totpCodeRequired: 'Код аутентификатора не может быть пустым',
|
totpCodeRequired: 'Код аутентификатора не может быть пустым',
|
||||||
enterTotpCode: 'Введите 6-значный код',
|
enterTotpCode: 'Введите 6-значный код',
|
||||||
recoveryCode: 'Код восстановления',
|
recoveryCode: 'Код восстановления',
|
||||||
recoveryCodeDescription: 'Введите один из одноразовых кодов восстановления, сохранённых при включении двухфакторной аутентификации',
|
recoveryCodeDescription:
|
||||||
|
'Введите один из одноразовых кодов восстановления, сохранённых при включении двухфакторной аутентификации',
|
||||||
recoveryCodeRequired: 'Код восстановления не может быть пустым',
|
recoveryCodeRequired: 'Код восстановления не может быть пустым',
|
||||||
enterRecoveryCodeValue: 'Введите код восстановления',
|
enterRecoveryCodeValue: 'Введите код восстановления',
|
||||||
},
|
},
|
||||||
@@ -1401,29 +1404,41 @@ const ruRU = {
|
|||||||
passkeyDeleteSuccess: 'Ключ доступа удален',
|
passkeyDeleteSuccess: 'Ключ доступа удален',
|
||||||
passkeyRenameSuccess: 'Ключ доступа успешно переименован',
|
passkeyRenameSuccess: 'Ключ доступа успешно переименован',
|
||||||
totpSectionTitle: 'Двухфакторная аутентификация',
|
totpSectionTitle: 'Двухфакторная аутентификация',
|
||||||
totpSectionDesc: 'Добавьте одноразовый пароль на основе времени как второй фактор входа',
|
totpSectionDesc:
|
||||||
totpEnabledDesc: 'Двухфакторная аутентификация включена · осталось кодов восстановления: {{count}}',
|
'Добавьте одноразовый пароль на основе времени как второй фактор входа',
|
||||||
|
totpEnabledDesc:
|
||||||
|
'Двухфакторная аутентификация включена · осталось кодов восстановления: {{count}}',
|
||||||
enableTotp: 'Включить',
|
enableTotp: 'Включить',
|
||||||
manageTotp: 'Управление',
|
manageTotp: 'Управление',
|
||||||
totpEnrollTitle: 'Включить двухфакторную аутентификацию',
|
totpEnrollTitle: 'Включить двухфакторную аутентификацию',
|
||||||
totpEnrollDesc: 'Отсканируйте QR-код в приложении-аутентификаторе и подтвердите сгенерированный код',
|
totpEnrollDesc:
|
||||||
|
'Отсканируйте QR-код в приложении-аутентификаторе и подтвердите сгенерированный код',
|
||||||
totpStartEnroll: 'Создать секрет',
|
totpStartEnroll: 'Создать секрет',
|
||||||
totpGeneratingSecret: 'Создание нового секрета…',
|
totpGeneratingSecret: 'Создание нового секрета…',
|
||||||
totpManageTitle: 'Двухфакторная аутентификация',
|
totpManageTitle: 'Двухфакторная аутентификация',
|
||||||
totpManageDesc: 'Перегенерируйте коды восстановления или отключите второй фактор.',
|
totpManageDesc:
|
||||||
|
'Перегенерируйте коды восстановления или отключите второй фактор.',
|
||||||
totpRegenerateCodes: 'Перегенерировать коды восстановления',
|
totpRegenerateCodes: 'Перегенерировать коды восстановления',
|
||||||
totpRegenerateDesc: 'Введите текущий код аутентификатора или код восстановления, чтобы получить новый набор.',
|
totpRegenerateDesc:
|
||||||
|
'Введите текущий код аутентификатора или код восстановления, чтобы получить новый набор.',
|
||||||
totpRecoveryCodesRegenerated: 'Новые коды восстановления созданы',
|
totpRecoveryCodesRegenerated: 'Новые коды восстановления созданы',
|
||||||
totpStatusDisabled: 'Не включено',
|
totpStatusDisabled: 'Не включено',
|
||||||
totpCodesRemaining: 'Осталось кодов восстановления: {{count}}',
|
totpCodesRemaining: 'Осталось кодов восстановления: {{count}}',
|
||||||
totpManagerSectionDesc: 'Владельцы и администраторы могут просматривать и сбрасывать второй фактор любого аккаунта.',
|
totpManagerSectionDesc:
|
||||||
|
'Владельцы и администраторы могут просматривать и сбрасывать второй фактор любого аккаунта.',
|
||||||
revokeTotp: 'Перепривязать',
|
revokeTotp: 'Перепривязать',
|
||||||
totpAdminResetTitle: 'Перепривязать двухфакторную аутентификацию для {{user}}',
|
totpAdminResetTitle:
|
||||||
totpAdminResetDesc: 'Попросите аккаунт отсканировать QR-код в приложении-аутентификаторе и ввести ниже 6-значный код для завершения.',
|
'Перепривязать двухфакторную аутентификацию для {{user}}',
|
||||||
totpAdminResetWarning: 'После начала перепривязки текущий аутентификатор {{user}} сразу перестанет работать.',
|
totpAdminResetDesc:
|
||||||
totpAdminResetHint: 'Если аккаунт сейчас не может войти, он может отсканировать этот QR-код в любом приложении-аутентификаторе.',
|
'Попросите аккаунт отсканировать QR-код в приложении-аутентификаторе и ввести ниже 6-значный код для завершения.',
|
||||||
totpAdminHandOverCodes: 'Передайте эти коды восстановления {{user}}. Они показываются только один раз.',
|
totpAdminResetWarning:
|
||||||
revokeTotpConfirm: 'Отключить двухфакторную аутентификацию для {{user}}? После этого вход будет возможен только по паролю.',
|
'После начала перепривязки текущий аутентификатор {{user}} сразу перестанет работать.',
|
||||||
|
totpAdminResetHint:
|
||||||
|
'Если аккаунт сейчас не может войти, он может отсканировать этот QR-код в любом приложении-аутентификаторе.',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'Передайте эти коды восстановления {{user}}. Они показываются только один раз.',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'Отключить двухфакторную аутентификацию для {{user}}? После этого вход будет возможен только по паролю.',
|
||||||
revokeTotpSuccess: 'Двухфакторная аутентификация сброшена',
|
revokeTotpSuccess: 'Двухфакторная аутентификация сброшена',
|
||||||
you: 'вы',
|
you: 'вы',
|
||||||
noAccounts: 'Нет аккаунтов для отображения',
|
noAccounts: 'Нет аккаунтов для отображения',
|
||||||
@@ -1437,13 +1452,16 @@ const ruRU = {
|
|||||||
totpLastUsed: 'Последняя проверка: {{date}}',
|
totpLastUsed: 'Последняя проверка: {{date}}',
|
||||||
totpNeverUsed: 'Ещё не использовалось',
|
totpNeverUsed: 'Ещё не использовалось',
|
||||||
disableTotp: 'Отключить двухфакторную аутентификацию',
|
disableTotp: 'Отключить двухфакторную аутентификацию',
|
||||||
disableTotpDesc: 'Введите текущий код аутентификатора или код восстановления, чтобы отключить двухфакторную аутентификацию',
|
disableTotpDesc:
|
||||||
|
'Введите текущий код аутентификатора или код восстановления, чтобы отключить двухфакторную аутентификацию',
|
||||||
totpEnabledSuccess: 'Двухфакторная аутентификация включена',
|
totpEnabledSuccess: 'Двухфакторная аутентификация включена',
|
||||||
totpDisabledSuccess: 'Двухфакторная аутентификация отключена',
|
totpDisabledSuccess: 'Двухфакторная аутентификация отключена',
|
||||||
totpInvalidCode: 'Неверный код, проверьте и попробуйте снова',
|
totpInvalidCode: 'Неверный код, проверьте и попробуйте снова',
|
||||||
totpRecoveryCodesTitle: 'Коды восстановления',
|
totpRecoveryCodesTitle: 'Коды восстановления',
|
||||||
totpRecoveryCodesDesc: 'Сохраните эти одноразовые коды восстановления в надёжном месте. Они показываются только один раз.',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: 'Каждый код работает один раз. Если вы потеряете аутентификатор и эти коды, вы потеряете доступ к входу.',
|
'Сохраните эти одноразовые коды восстановления в надёжном месте. Они показываются только один раз.',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'Каждый код работает один раз. Если вы потеряете аутентификатор и эти коды, вы потеряете доступ к входу.',
|
||||||
totpSavedCodes: 'Я сохранил эти коды',
|
totpSavedCodes: 'Я сохранил эти коды',
|
||||||
regenerateRecoveryCodes: 'Перегенерировать коды восстановления',
|
regenerateRecoveryCodes: 'Перегенерировать коды восстановления',
|
||||||
bindSpaceFailed: 'Не удалось привязать аккаунт LangBot',
|
bindSpaceFailed: 'Не удалось привязать аккаунт LangBot',
|
||||||
|
|||||||
@@ -1308,7 +1308,8 @@ const thTH = {
|
|||||||
totpCodeRequired: 'รหัสจากแอปยืนยันตัวตนต้องไม่ว่าง',
|
totpCodeRequired: 'รหัสจากแอปยืนยันตัวตนต้องไม่ว่าง',
|
||||||
enterTotpCode: 'ป้อนรหัส 6 หลัก',
|
enterTotpCode: 'ป้อนรหัส 6 หลัก',
|
||||||
recoveryCode: 'รหัสกู้คืน',
|
recoveryCode: 'รหัสกู้คืน',
|
||||||
recoveryCodeDescription: 'ป้อนรหัสกู้คืนแบบใช้ครั้งเดียวที่บันทึกไว้เมื่อเปิดใช้การยืนยันสองขั้นตอน',
|
recoveryCodeDescription:
|
||||||
|
'ป้อนรหัสกู้คืนแบบใช้ครั้งเดียวที่บันทึกไว้เมื่อเปิดใช้การยืนยันสองขั้นตอน',
|
||||||
recoveryCodeRequired: 'รหัสกู้คืนต้องไม่ว่าง',
|
recoveryCodeRequired: 'รหัสกู้คืนต้องไม่ว่าง',
|
||||||
enterRecoveryCodeValue: 'ป้อนรหัสกู้คืน',
|
enterRecoveryCodeValue: 'ป้อนรหัสกู้คืน',
|
||||||
},
|
},
|
||||||
@@ -1371,29 +1372,39 @@ const thTH = {
|
|||||||
passkeyDeleteSuccess: 'ลบพาสคีย์แล้ว',
|
passkeyDeleteSuccess: 'ลบพาสคีย์แล้ว',
|
||||||
passkeyRenameSuccess: 'เปลี่ยนชื่อพาสคีย์สำเร็จ',
|
passkeyRenameSuccess: 'เปลี่ยนชื่อพาสคีย์สำเร็จ',
|
||||||
totpSectionTitle: 'การยืนยันสองขั้นตอน',
|
totpSectionTitle: 'การยืนยันสองขั้นตอน',
|
||||||
totpSectionDesc: 'เพิ่มรหัสผ่านใช้ครั้งเดียวตามเวลาเป็นปัจจัยที่สองในการเข้าสู่ระบบ',
|
totpSectionDesc:
|
||||||
totpEnabledDesc: 'เปิดใช้การยืนยันสองขั้นตอนแล้ว · เหลือรหัสกู้คืน {{count}} รหัส',
|
'เพิ่มรหัสผ่านใช้ครั้งเดียวตามเวลาเป็นปัจจัยที่สองในการเข้าสู่ระบบ',
|
||||||
|
totpEnabledDesc:
|
||||||
|
'เปิดใช้การยืนยันสองขั้นตอนแล้ว · เหลือรหัสกู้คืน {{count}} รหัส',
|
||||||
enableTotp: 'เปิดใช้',
|
enableTotp: 'เปิดใช้',
|
||||||
manageTotp: 'จัดการ',
|
manageTotp: 'จัดการ',
|
||||||
totpEnrollTitle: 'เปิดใช้การยืนยันสองขั้นตอน',
|
totpEnrollTitle: 'เปิดใช้การยืนยันสองขั้นตอน',
|
||||||
totpEnrollDesc: 'สแกนคิวอาร์โค้ดด้วยแอปยืนยันตัวตน แล้วยืนยันรหัสที่สร้างขึ้น',
|
totpEnrollDesc:
|
||||||
|
'สแกนคิวอาร์โค้ดด้วยแอปยืนยันตัวตน แล้วยืนยันรหัสที่สร้างขึ้น',
|
||||||
totpStartEnroll: 'สร้างรหัสลับ',
|
totpStartEnroll: 'สร้างรหัสลับ',
|
||||||
totpGeneratingSecret: 'กำลังสร้างรหัสลับใหม่…',
|
totpGeneratingSecret: 'กำลังสร้างรหัสลับใหม่…',
|
||||||
totpManageTitle: 'การยืนยันสองขั้นตอน',
|
totpManageTitle: 'การยืนยันสองขั้นตอน',
|
||||||
totpManageDesc: 'สร้างรหัสกู้คืนใหม่ หรือปิดการยืนยันสองขั้นตอน',
|
totpManageDesc: 'สร้างรหัสกู้คืนใหม่ หรือปิดการยืนยันสองขั้นตอน',
|
||||||
totpRegenerateCodes: 'สร้างรหัสกู้คืนใหม่',
|
totpRegenerateCodes: 'สร้างรหัสกู้คืนใหม่',
|
||||||
totpRegenerateDesc: 'ป้อนรหัสจากแอปหรือรหัสกู้คืนปัจจุบันเพื่อออกชุดรหัสใหม่',
|
totpRegenerateDesc:
|
||||||
|
'ป้อนรหัสจากแอปหรือรหัสกู้คืนปัจจุบันเพื่อออกชุดรหัสใหม่',
|
||||||
totpRecoveryCodesRegenerated: 'สร้างรหัสกู้คืนใหม่แล้ว',
|
totpRecoveryCodesRegenerated: 'สร้างรหัสกู้คืนใหม่แล้ว',
|
||||||
totpStatusDisabled: 'ยังไม่เปิดใช้',
|
totpStatusDisabled: 'ยังไม่เปิดใช้',
|
||||||
totpCodesRemaining: 'เหลือรหัสกู้คืน {{count}} รหัส',
|
totpCodesRemaining: 'เหลือรหัสกู้คืน {{count}} รหัส',
|
||||||
totpManagerSectionDesc: 'เจ้าของและผู้ดูแลสามารถตรวจสอบและรีเซ็ตการยืนยันสองขั้นตอนของบัญชีใดก็ได้',
|
totpManagerSectionDesc:
|
||||||
|
'เจ้าของและผู้ดูแลสามารถตรวจสอบและรีเซ็ตการยืนยันสองขั้นตอนของบัญชีใดก็ได้',
|
||||||
revokeTotp: 'ผูกใหม่',
|
revokeTotp: 'ผูกใหม่',
|
||||||
totpAdminResetTitle: 'ผูกการยืนยันสองขั้นตอนใหม่ให้ {{user}}',
|
totpAdminResetTitle: 'ผูกการยืนยันสองขั้นตอนใหม่ให้ {{user}}',
|
||||||
totpAdminResetDesc: 'ให้บัญชีนั้นสแกนคิวอาร์โค้ดด้วยแอปยืนยันตัวตน แล้วกรอกรหัส 6 หลักด้านล่างเพื่อเสร็จสิ้นการผูก',
|
totpAdminResetDesc:
|
||||||
totpAdminResetWarning: 'เมื่อเริ่มผูกใหม่ แอปยืนยันตัวตนเดิมของ {{user}} จะใช้งานไม่ได้ทันที',
|
'ให้บัญชีนั้นสแกนคิวอาร์โค้ดด้วยแอปยืนยันตัวตน แล้วกรอกรหัส 6 หลักด้านล่างเพื่อเสร็จสิ้นการผูก',
|
||||||
totpAdminResetHint: 'หากบัญชีนั้นยังเข้าสู่ระบบไม่ได้ในตอนนี้ สามารถสแกนคิวอาร์โค้ดนี้ในแอปยืนยันตัวตนใดก็ได้',
|
totpAdminResetWarning:
|
||||||
totpAdminHandOverCodes: 'ส่งรหัสกู้คืนเหล่านี้ให้ {{user}} โดยจะแสดงเพียงครั้งเดียว',
|
'เมื่อเริ่มผูกใหม่ แอปยืนยันตัวตนเดิมของ {{user}} จะใช้งานไม่ได้ทันที',
|
||||||
revokeTotpConfirm: 'ปิดการยืนยันสองขั้นตอนของ {{user}} หรือไม่ หลังจากนั้นจะเข้าสู่ระบบด้วยรหัสผ่านเท่านั้น',
|
totpAdminResetHint:
|
||||||
|
'หากบัญชีนั้นยังเข้าสู่ระบบไม่ได้ในตอนนี้ สามารถสแกนคิวอาร์โค้ดนี้ในแอปยืนยันตัวตนใดก็ได้',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'ส่งรหัสกู้คืนเหล่านี้ให้ {{user}} โดยจะแสดงเพียงครั้งเดียว',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'ปิดการยืนยันสองขั้นตอนของ {{user}} หรือไม่ หลังจากนั้นจะเข้าสู่ระบบด้วยรหัสผ่านเท่านั้น',
|
||||||
revokeTotpSuccess: 'รีเซ็ตการยืนยันสองขั้นตอนแล้ว',
|
revokeTotpSuccess: 'รีเซ็ตการยืนยันสองขั้นตอนแล้ว',
|
||||||
you: 'คุณ',
|
you: 'คุณ',
|
||||||
noAccounts: 'ไม่มีบัญชีที่จะแสดง',
|
noAccounts: 'ไม่มีบัญชีที่จะแสดง',
|
||||||
@@ -1407,13 +1418,16 @@ const thTH = {
|
|||||||
totpLastUsed: 'ยืนยันล่าสุด: {{date}}',
|
totpLastUsed: 'ยืนยันล่าสุด: {{date}}',
|
||||||
totpNeverUsed: 'ยังไม่เคยใช้',
|
totpNeverUsed: 'ยังไม่เคยใช้',
|
||||||
disableTotp: 'ปิดใช้การยืนยันสองขั้นตอน',
|
disableTotp: 'ปิดใช้การยืนยันสองขั้นตอน',
|
||||||
disableTotpDesc: 'ป้อนรหัสจากแอปยืนยันตัวตนปัจจุบันหรือรหัสกู้คืนเพื่อปิดการยืนยันสองขั้นตอน',
|
disableTotpDesc:
|
||||||
|
'ป้อนรหัสจากแอปยืนยันตัวตนปัจจุบันหรือรหัสกู้คืนเพื่อปิดการยืนยันสองขั้นตอน',
|
||||||
totpEnabledSuccess: 'เปิดใช้การยืนยันสองขั้นตอนแล้ว',
|
totpEnabledSuccess: 'เปิดใช้การยืนยันสองขั้นตอนแล้ว',
|
||||||
totpDisabledSuccess: 'ปิดใช้การยืนยันสองขั้นตอนแล้ว',
|
totpDisabledSuccess: 'ปิดใช้การยืนยันสองขั้นตอนแล้ว',
|
||||||
totpInvalidCode: 'รหัสไม่ถูกต้อง กรุณาตรวจสอบแล้วลองใหม่',
|
totpInvalidCode: 'รหัสไม่ถูกต้อง กรุณาตรวจสอบแล้วลองใหม่',
|
||||||
totpRecoveryCodesTitle: 'รหัสกู้คืน',
|
totpRecoveryCodesTitle: 'รหัสกู้คืน',
|
||||||
totpRecoveryCodesDesc: 'เก็บรหัสกู้คืนแบบใช้ครั้งเดียวเหล่านี้ไว้ในที่ปลอดภัย จะแสดงเพียงครั้งเดียว',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: 'รหัสแต่ละรหัสใช้ได้ครั้งเดียว หากคุณทำแอปยืนยันตัวตนและรหัสเหล่านี้หาย คุณจะไม่สามารถเข้าสู่ระบบได้',
|
'เก็บรหัสกู้คืนแบบใช้ครั้งเดียวเหล่านี้ไว้ในที่ปลอดภัย จะแสดงเพียงครั้งเดียว',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'รหัสแต่ละรหัสใช้ได้ครั้งเดียว หากคุณทำแอปยืนยันตัวตนและรหัสเหล่านี้หาย คุณจะไม่สามารถเข้าสู่ระบบได้',
|
||||||
totpSavedCodes: 'ฉันบันทึกรหัสเหล่านี้แล้ว',
|
totpSavedCodes: 'ฉันบันทึกรหัสเหล่านี้แล้ว',
|
||||||
regenerateRecoveryCodes: 'สร้างรหัสกู้คืนใหม่',
|
regenerateRecoveryCodes: 'สร้างรหัสกู้คืนใหม่',
|
||||||
bindSpaceFailed: 'ผูกบัญชี LangBot ล้มเหลว',
|
bindSpaceFailed: 'ผูกบัญชี LangBot ล้มเหลว',
|
||||||
|
|||||||
@@ -1330,7 +1330,8 @@ const viVN = {
|
|||||||
totpCodeRequired: 'Mã xác thực không được để trống',
|
totpCodeRequired: 'Mã xác thực không được để trống',
|
||||||
enterTotpCode: 'Nhập mã 6 chữ số',
|
enterTotpCode: 'Nhập mã 6 chữ số',
|
||||||
recoveryCode: 'Mã khôi phục',
|
recoveryCode: 'Mã khôi phục',
|
||||||
recoveryCodeDescription: 'Nhập một trong các mã khôi phục dùng một lần bạn đã lưu khi bật xác minh hai bước',
|
recoveryCodeDescription:
|
||||||
|
'Nhập một trong các mã khôi phục dùng một lần bạn đã lưu khi bật xác minh hai bước',
|
||||||
recoveryCodeRequired: 'Mã khôi phục không được để trống',
|
recoveryCodeRequired: 'Mã khôi phục không được để trống',
|
||||||
enterRecoveryCodeValue: 'Nhập mã khôi phục',
|
enterRecoveryCodeValue: 'Nhập mã khôi phục',
|
||||||
},
|
},
|
||||||
@@ -1394,29 +1395,38 @@ const viVN = {
|
|||||||
passkeyDeleteSuccess: 'Đã xóa mã khóa truy cập',
|
passkeyDeleteSuccess: 'Đã xóa mã khóa truy cập',
|
||||||
passkeyRenameSuccess: 'Đã đổi tên mã khóa truy cập thành công',
|
passkeyRenameSuccess: 'Đã đổi tên mã khóa truy cập thành công',
|
||||||
totpSectionTitle: 'Xác minh hai bước',
|
totpSectionTitle: 'Xác minh hai bước',
|
||||||
totpSectionDesc: 'Thêm mật khẩu dùng một lần theo thời gian làm yếu tố đăng nhập thứ hai',
|
totpSectionDesc:
|
||||||
|
'Thêm mật khẩu dùng một lần theo thời gian làm yếu tố đăng nhập thứ hai',
|
||||||
totpEnabledDesc: 'Đã bật xác minh hai bước · còn {{count}} mã khôi phục',
|
totpEnabledDesc: 'Đã bật xác minh hai bước · còn {{count}} mã khôi phục',
|
||||||
enableTotp: 'Bật',
|
enableTotp: 'Bật',
|
||||||
manageTotp: 'Quản lý',
|
manageTotp: 'Quản lý',
|
||||||
totpEnrollTitle: 'Bật xác minh hai bước',
|
totpEnrollTitle: 'Bật xác minh hai bước',
|
||||||
totpEnrollDesc: 'Quét mã QR bằng ứng dụng xác thực, sau đó xác nhận mã được tạo',
|
totpEnrollDesc:
|
||||||
|
'Quét mã QR bằng ứng dụng xác thực, sau đó xác nhận mã được tạo',
|
||||||
totpStartEnroll: 'Tạo khóa bí mật',
|
totpStartEnroll: 'Tạo khóa bí mật',
|
||||||
totpGeneratingSecret: 'Đang tạo khóa bí mật mới…',
|
totpGeneratingSecret: 'Đang tạo khóa bí mật mới…',
|
||||||
totpManageTitle: 'Xác minh hai bước',
|
totpManageTitle: 'Xác minh hai bước',
|
||||||
totpManageDesc: 'Tạo lại mã khôi phục hoặc tắt yếu tố xác minh thứ hai.',
|
totpManageDesc: 'Tạo lại mã khôi phục hoặc tắt yếu tố xác minh thứ hai.',
|
||||||
totpRegenerateCodes: 'Tạo lại mã khôi phục',
|
totpRegenerateCodes: 'Tạo lại mã khôi phục',
|
||||||
totpRegenerateDesc: 'Nhập mã xác thực hoặc mã khôi phục hiện tại để tạo bộ mã mới.',
|
totpRegenerateDesc:
|
||||||
|
'Nhập mã xác thực hoặc mã khôi phục hiện tại để tạo bộ mã mới.',
|
||||||
totpRecoveryCodesRegenerated: 'Đã tạo mã khôi phục mới',
|
totpRecoveryCodesRegenerated: 'Đã tạo mã khôi phục mới',
|
||||||
totpStatusDisabled: 'Chưa bật',
|
totpStatusDisabled: 'Chưa bật',
|
||||||
totpCodesRemaining: 'Còn {{count}} mã khôi phục',
|
totpCodesRemaining: 'Còn {{count}} mã khôi phục',
|
||||||
totpManagerSectionDesc: 'Chủ sở hữu và quản trị viên có thể xem và đặt lại yếu tố thứ hai của bất kỳ tài khoản nào.',
|
totpManagerSectionDesc:
|
||||||
|
'Chủ sở hữu và quản trị viên có thể xem và đặt lại yếu tố thứ hai của bất kỳ tài khoản nào.',
|
||||||
revokeTotp: 'Liên kết lại',
|
revokeTotp: 'Liên kết lại',
|
||||||
totpAdminResetTitle: 'Liên kết lại xác thực hai bước cho {{user}}',
|
totpAdminResetTitle: 'Liên kết lại xác thực hai bước cho {{user}}',
|
||||||
totpAdminResetDesc: 'Yêu cầu tài khoản đó quét mã QR bằng ứng dụng xác thực, rồi nhập mã 6 chữ số bên dưới để hoàn tất.',
|
totpAdminResetDesc:
|
||||||
totpAdminResetWarning: 'Khi bắt đầu liên kết lại, ứng dụng xác thực hiện tại của {{user}} sẽ ngừng hoạt động ngay.',
|
'Yêu cầu tài khoản đó quét mã QR bằng ứng dụng xác thực, rồi nhập mã 6 chữ số bên dưới để hoàn tất.',
|
||||||
totpAdminResetHint: 'Nếu tài khoản đó hiện không thể đăng nhập, họ có thể quét mã QR này bằng bất kỳ ứng dụng xác thực nào.',
|
totpAdminResetWarning:
|
||||||
totpAdminHandOverCodes: 'Hãy chuyển các mã khôi phục này cho {{user}}. Chúng chỉ hiển thị một lần.',
|
'Khi bắt đầu liên kết lại, ứng dụng xác thực hiện tại của {{user}} sẽ ngừng hoạt động ngay.',
|
||||||
revokeTotpConfirm: 'Tắt xác minh hai bước cho {{user}}? Sau đó họ chỉ cần mật khẩu để đăng nhập.',
|
totpAdminResetHint:
|
||||||
|
'Nếu tài khoản đó hiện không thể đăng nhập, họ có thể quét mã QR này bằng bất kỳ ứng dụng xác thực nào.',
|
||||||
|
totpAdminHandOverCodes:
|
||||||
|
'Hãy chuyển các mã khôi phục này cho {{user}}. Chúng chỉ hiển thị một lần.',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'Tắt xác minh hai bước cho {{user}}? Sau đó họ chỉ cần mật khẩu để đăng nhập.',
|
||||||
revokeTotpSuccess: 'Đã đặt lại xác minh hai bước',
|
revokeTotpSuccess: 'Đã đặt lại xác minh hai bước',
|
||||||
you: 'bạn',
|
you: 'bạn',
|
||||||
noAccounts: 'Không có tài khoản để hiển thị',
|
noAccounts: 'Không có tài khoản để hiển thị',
|
||||||
@@ -1430,13 +1440,16 @@ const viVN = {
|
|||||||
totpLastUsed: 'Xác minh gần nhất: {{date}}',
|
totpLastUsed: 'Xác minh gần nhất: {{date}}',
|
||||||
totpNeverUsed: 'Chưa sử dụng',
|
totpNeverUsed: 'Chưa sử dụng',
|
||||||
disableTotp: 'Tắt xác minh hai bước',
|
disableTotp: 'Tắt xác minh hai bước',
|
||||||
disableTotpDesc: 'Nhập mã từ ứng dụng xác thực hiện tại hoặc mã khôi phục để tắt xác minh hai bước',
|
disableTotpDesc:
|
||||||
|
'Nhập mã từ ứng dụng xác thực hiện tại hoặc mã khôi phục để tắt xác minh hai bước',
|
||||||
totpEnabledSuccess: 'Đã bật xác minh hai bước',
|
totpEnabledSuccess: 'Đã bật xác minh hai bước',
|
||||||
totpDisabledSuccess: 'Đã tắt xác minh hai bước',
|
totpDisabledSuccess: 'Đã tắt xác minh hai bước',
|
||||||
totpInvalidCode: 'Mã không hợp lệ, vui lòng kiểm tra và thử lại',
|
totpInvalidCode: 'Mã không hợp lệ, vui lòng kiểm tra và thử lại',
|
||||||
totpRecoveryCodesTitle: 'Mã khôi phục',
|
totpRecoveryCodesTitle: 'Mã khôi phục',
|
||||||
totpRecoveryCodesDesc: 'Lưu các mã khôi phục dùng một lần này ở nơi an toàn. Chúng chỉ hiển thị một lần.',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: 'Mỗi mã chỉ dùng được một lần. Nếu bạn mất ứng dụng xác thực và các mã này, bạn sẽ mất quyền truy cập đăng nhập.',
|
'Lưu các mã khôi phục dùng một lần này ở nơi an toàn. Chúng chỉ hiển thị một lần.',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'Mỗi mã chỉ dùng được một lần. Nếu bạn mất ứng dụng xác thực và các mã này, bạn sẽ mất quyền truy cập đăng nhập.',
|
||||||
totpSavedCodes: 'Tôi đã lưu các mã này',
|
totpSavedCodes: 'Tôi đã lưu các mã này',
|
||||||
regenerateRecoveryCodes: 'Tạo lại mã khôi phục',
|
regenerateRecoveryCodes: 'Tạo lại mã khôi phục',
|
||||||
bindSpaceFailed: 'Liên kết tài khoản LangBot thất bại',
|
bindSpaceFailed: 'Liên kết tài khoản LangBot thất bại',
|
||||||
|
|||||||
@@ -1334,18 +1334,23 @@ const zhHans = {
|
|||||||
totpManageTitle: '两步验证',
|
totpManageTitle: '两步验证',
|
||||||
totpManageDesc: '重新生成恢复代码,或关闭两步验证。',
|
totpManageDesc: '重新生成恢复代码,或关闭两步验证。',
|
||||||
totpRegenerateCodes: '重新生成恢复代码',
|
totpRegenerateCodes: '重新生成恢复代码',
|
||||||
totpRegenerateDesc: '请输入当前验证器验证码或恢复代码,以生成一组新的恢复代码。',
|
totpRegenerateDesc:
|
||||||
|
'请输入当前验证器验证码或恢复代码,以生成一组新的恢复代码。',
|
||||||
totpRecoveryCodesRegenerated: '已生成新的恢复代码',
|
totpRecoveryCodesRegenerated: '已生成新的恢复代码',
|
||||||
totpStatusDisabled: '未启用',
|
totpStatusDisabled: '未启用',
|
||||||
totpCodesRemaining: '剩余 {{count}} 个恢复代码',
|
totpCodesRemaining: '剩余 {{count}} 个恢复代码',
|
||||||
totpManagerSectionDesc: '所有者和管理员可以查看并重置任意账户的两步验证。',
|
totpManagerSectionDesc: '所有者和管理员可以查看并重置任意账户的两步验证。',
|
||||||
revokeTotp: '重新绑定',
|
revokeTotp: '重新绑定',
|
||||||
totpAdminResetTitle: '重新绑定 {{user}} 的两步验证',
|
totpAdminResetTitle: '重新绑定 {{user}} 的两步验证',
|
||||||
totpAdminResetDesc: '请让该账户用身份验证器扫描下方二维码,再把生成的 6 位验证码填入下方完成绑定。',
|
totpAdminResetDesc:
|
||||||
|
'请让该账户用身份验证器扫描下方二维码,再把生成的 6 位验证码填入下方完成绑定。',
|
||||||
totpAdminResetWarning: '开始绑定后,{{user}} 原来的身份验证器会立即失效。',
|
totpAdminResetWarning: '开始绑定后,{{user}} 原来的身份验证器会立即失效。',
|
||||||
totpAdminResetHint: '如果该账户当前无法登录,可让其在任意身份验证器中扫描此二维码。',
|
totpAdminResetHint:
|
||||||
totpAdminHandOverCodes: '请把这些恢复代码转交给 {{user}},它们只会显示一次。',
|
'如果该账户当前无法登录,可让其在任意身份验证器中扫描此二维码。',
|
||||||
revokeTotpConfirm: '确定要关闭 {{user}} 的两步验证吗?关闭后该账户仅凭密码即可登录。',
|
totpAdminHandOverCodes:
|
||||||
|
'请把这些恢复代码转交给 {{user}},它们只会显示一次。',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'确定要关闭 {{user}} 的两步验证吗?关闭后该账户仅凭密码即可登录。',
|
||||||
revokeTotpSuccess: '已重置两步验证',
|
revokeTotpSuccess: '已重置两步验证',
|
||||||
you: '你',
|
you: '你',
|
||||||
noAccounts: '暂无账户',
|
noAccounts: '暂无账户',
|
||||||
@@ -1364,8 +1369,10 @@ const zhHans = {
|
|||||||
totpDisabledSuccess: '两步验证已停用',
|
totpDisabledSuccess: '两步验证已停用',
|
||||||
totpInvalidCode: '验证码无效,请检查后重试',
|
totpInvalidCode: '验证码无效,请检查后重试',
|
||||||
totpRecoveryCodesTitle: '恢复代码',
|
totpRecoveryCodesTitle: '恢复代码',
|
||||||
totpRecoveryCodesDesc: '请将这些一次性恢复代码保存在安全的地方,它们只会显示一次。',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: '每个代码只能使用一次。如果验证器和这些代码都丢失,您将无法登录。',
|
'请将这些一次性恢复代码保存在安全的地方,它们只会显示一次。',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'每个代码只能使用一次。如果验证器和这些代码都丢失,您将无法登录。',
|
||||||
totpSavedCodes: '我已保存这些代码',
|
totpSavedCodes: '我已保存这些代码',
|
||||||
regenerateRecoveryCodes: '重新生成恢复代码',
|
regenerateRecoveryCodes: '重新生成恢复代码',
|
||||||
bindSpaceFailed: '绑定 LangBot 账号失败',
|
bindSpaceFailed: '绑定 LangBot 账号失败',
|
||||||
|
|||||||
@@ -1335,18 +1335,23 @@ const zhHant = {
|
|||||||
totpManageTitle: '兩步驗證',
|
totpManageTitle: '兩步驗證',
|
||||||
totpManageDesc: '重新產生恢復代碼,或關閉兩步驗證。',
|
totpManageDesc: '重新產生恢復代碼,或關閉兩步驗證。',
|
||||||
totpRegenerateCodes: '重新產生恢復代碼',
|
totpRegenerateCodes: '重新產生恢復代碼',
|
||||||
totpRegenerateDesc: '請輸入目前驗證器驗證碼或恢復代碼,以產生一組新的恢復代碼。',
|
totpRegenerateDesc:
|
||||||
|
'請輸入目前驗證器驗證碼或恢復代碼,以產生一組新的恢復代碼。',
|
||||||
totpRecoveryCodesRegenerated: '已產生新的恢復代碼',
|
totpRecoveryCodesRegenerated: '已產生新的恢復代碼',
|
||||||
totpStatusDisabled: '未啟用',
|
totpStatusDisabled: '未啟用',
|
||||||
totpCodesRemaining: '剩餘 {{count}} 個恢復代碼',
|
totpCodesRemaining: '剩餘 {{count}} 個恢復代碼',
|
||||||
totpManagerSectionDesc: '擁有者與管理員可以檢視並重設任意帳號的兩步驗證。',
|
totpManagerSectionDesc: '擁有者與管理員可以檢視並重設任意帳號的兩步驗證。',
|
||||||
revokeTotp: '重新綁定',
|
revokeTotp: '重新綁定',
|
||||||
totpAdminResetTitle: '重新綁定 {{user}} 的兩步驗證',
|
totpAdminResetTitle: '重新綁定 {{user}} 的兩步驗證',
|
||||||
totpAdminResetDesc: '請讓該帳號用驗證器掃描下方二維碼,再把產生的 6 位驗證碼填入下方完成綁定。',
|
totpAdminResetDesc:
|
||||||
|
'請讓該帳號用驗證器掃描下方二維碼,再把產生的 6 位驗證碼填入下方完成綁定。',
|
||||||
totpAdminResetWarning: '開始綁定後,{{user}} 原本的驗證器會立即失效。',
|
totpAdminResetWarning: '開始綁定後,{{user}} 原本的驗證器會立即失效。',
|
||||||
totpAdminResetHint: '如果該帳號目前無法登入,可讓其在任意驗證器中掃描此二維碼。',
|
totpAdminResetHint:
|
||||||
totpAdminHandOverCodes: '請將這些恢復代碼轉交給 {{user}},它們只會顯示一次。',
|
'如果該帳號目前無法登入,可讓其在任意驗證器中掃描此二維碼。',
|
||||||
revokeTotpConfirm: '確定要關閉 {{user}} 的兩步驗證嗎?關閉後該帳號僅憑密碼即可登入。',
|
totpAdminHandOverCodes:
|
||||||
|
'請將這些恢復代碼轉交給 {{user}},它們只會顯示一次。',
|
||||||
|
revokeTotpConfirm:
|
||||||
|
'確定要關閉 {{user}} 的兩步驗證嗎?關閉後該帳號僅憑密碼即可登入。',
|
||||||
revokeTotpSuccess: '已重設兩步驗證',
|
revokeTotpSuccess: '已重設兩步驗證',
|
||||||
you: '你',
|
you: '你',
|
||||||
noAccounts: '暫無帳號',
|
noAccounts: '暫無帳號',
|
||||||
@@ -1365,8 +1370,10 @@ const zhHant = {
|
|||||||
totpDisabledSuccess: '兩步驗證已停用',
|
totpDisabledSuccess: '兩步驗證已停用',
|
||||||
totpInvalidCode: '驗證碼無效,請檢查後重試',
|
totpInvalidCode: '驗證碼無效,請檢查後重試',
|
||||||
totpRecoveryCodesTitle: '恢復代碼',
|
totpRecoveryCodesTitle: '恢復代碼',
|
||||||
totpRecoveryCodesDesc: '請將這些一次性恢復代碼保存在安全的地方,它們只會顯示一次。',
|
totpRecoveryCodesDesc:
|
||||||
totpRecoveryCodesWarning: '每個代碼只能使用一次。如果驗證器和這些代碼都遺失,您將無法登入。',
|
'請將這些一次性恢復代碼保存在安全的地方,它們只會顯示一次。',
|
||||||
|
totpRecoveryCodesWarning:
|
||||||
|
'每個代碼只能使用一次。如果驗證器和這些代碼都遺失,您將無法登入。',
|
||||||
totpSavedCodes: '我已儲存這些代碼',
|
totpSavedCodes: '我已儲存這些代碼',
|
||||||
regenerateRecoveryCodes: '重新產生恢復代碼',
|
regenerateRecoveryCodes: '重新產生恢復代碼',
|
||||||
bindSpaceFailed: '綁定 LangBot 帳號失敗',
|
bindSpaceFailed: '綁定 LangBot 帳號失敗',
|
||||||
|
|||||||
Reference in New Issue
Block a user