i18n: extract the settings screens your pass didn't reach (settings)

Part of #145.

`8c9bf0d` localized the settings views themselves; these eight files sit
in the same zone but were not in that pass: the subscription modal, the
child-user creation and password-change modals, both payment result
views, developer settings, and the backup/restore and account-deletion
modals.

Extends the `settings` namespace you established, following its key
naming — 59 keys added to `en/settings.json`. `src/i18n/config.js` is
untouched.

English only — no translations, no behaviour change. Every t() value is
checked against this branch's base: the string must appear
character-for-character in the code it replaces (71 call sites).

One deliberate structural detail: in `BackupRestoreModal` the warning
reads `<strong>Warning:</strong> Restoring a backup will…`. The label and
the sentence are separate keys and the `<strong>` stays in the JSX, so the
emphasis survives and no markup ends up in the dictionary.

If you'd rather keep developer settings hardcoded, say so and I'll drop
that file — it is the one screen here an end user never sees.
This commit is contained in:
everysingletear
2026-08-13 10:18:10 +08:00
parent 7eba12e718
commit 0e98a556da
9 changed files with 162 additions and 76 deletions

View File

@@ -2,8 +2,10 @@ import { FormControl, FormHelperText, Input, Typography } from '@mui/joy'
import { useEffect, useState } from 'react'
import ModalActions from '../../../components/common/ModalActions'
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
import { useTranslation } from 'react-i18next'
function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
const { t } = useTranslation('settings')
const { ResponsiveModal } = useResponsiveModal()
const [childName, setChildName] = useState('')
@@ -19,39 +21,38 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
if (touched.childName) {
if (!childName.trim()) {
newErrors.childName = 'Sub account name is required'
newErrors.childName = t('childUsers.errNameRequired')
} else if (childName.length < 2) {
newErrors.childName = 'Sub account name must be at least 2 characters'
newErrors.childName = t('childUsers.errNameMin')
} else if (childName.length > 20) {
newErrors.childName = 'Sub account name must be less than 20 characters'
newErrors.childName = t('childUsers.errNameMax')
} else if (!/^[a-z.-]+$/.test(childName)) {
newErrors.childName =
'Sub account name can only contain lowercase letters, dot and dash'
newErrors.childName = t('childUsers.errNameChars')
}
}
if (touched.password) {
if (!password) {
newErrors.password = 'Password is required'
newErrors.password = t('childUsers.errPasswordRequired')
} else if (password.length < 8) {
newErrors.password = 'Password must be between 8 and 64 characters'
newErrors.password = t('childUsers.errPasswordLength')
} else if (password.length > 64) {
newErrors.password = 'Password must be between 8 and 64 characters'
newErrors.password = t('childUsers.errPasswordLength')
}
}
if (touched.confirmPassword) {
if (password !== confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match'
newErrors.confirmPassword = t('childUsers.errPasswordMatch')
}
}
if (touched.displayName && displayName.length > 50) {
newErrors.displayName = 'Display name must be less than 50 characters'
newErrors.displayName = t('childUsers.errDisplayNameMax')
}
setErrors(newErrors)
}, [childName, displayName, password, confirmPassword, touched])
}, [childName, displayName, password, confirmPassword, touched, t])
const handleSubmit = async () => {
setTouched({
@@ -101,7 +102,7 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
<ResponsiveModal
open={isOpen}
onClose={handleClose}
title='Create Sub Account'
title={t('childUsers.createTitle')}
description='Create a login that can complete tasks assigned to this account.'
size='md'
closeOnBackdrop={!isSubmitting}
@@ -109,12 +110,12 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
footer={
<ModalActions
secondary={{
label: 'Cancel',
label: t('accountSettings.cancel'),
onClick: handleClose,
disabled: isSubmitting,
}}
primary={{
label: 'Create Account',
label: t('childUsers.createButton'),
onClick: handleSubmit,
disabled: !isValid || isSubmitting,
loading: isSubmitting,
@@ -124,14 +125,14 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
>
<FormControl error={!!errors.childName} sx={{ mb: 2 }}>
<Typography level='body2' mb={1}>
Sub Account Name *
{t('childUsers.nameLabel')}
</Typography>
<Input
required
fullWidth
id='childName'
name='childName'
placeholder='Enter sub account name (e.g., sarah)'
placeholder={t('childUsers.namePlaceholder')}
value={childName}
onChange={e => {
setChildName(e.target.value)
@@ -145,13 +146,13 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
<FormControl error={!!errors.displayName} sx={{ mb: 2 }}>
<Typography level='body2' mb={1}>
Display Name
{t('childUsers.displayNameLabel')}
</Typography>
<Input
fullWidth
id='displayName'
name='displayName'
placeholder='Display name (optional, defaults to sub account name)'
placeholder={t('childUsers.displayNamePlaceholder')}
value={displayName}
onChange={e => {
setDisplayName(e.target.value)
@@ -165,7 +166,7 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
<FormControl error={!!errors.password} sx={{ mb: 2 }}>
<Typography level='body2' mb={1}>
Password *
{t('childUsers.passwordLabel')}
</Typography>
<Input
required
@@ -173,7 +174,7 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
name='password'
type='password'
id='password'
placeholder='Enter password (8-64 characters)'
placeholder={t('childUsers.passwordPlaceholder')}
value={password}
onChange={e => {
setPassword(e.target.value)
@@ -185,7 +186,7 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
<FormControl error={!!errors.confirmPassword} sx={{ mb: 3 }}>
<Typography level='body2' mb={1}>
Confirm Password *
{t('childUsers.confirmPasswordLabel')}
</Typography>
<Input
required
@@ -193,7 +194,7 @@ function CreateChildUserModal({ isOpen, onClose, onSuccess }) {
name='confirmPassword'
type='password'
id='confirmPassword'
placeholder='Confirm password'
placeholder={t('childUsers.confirmPasswordPlaceholder')}
value={confirmPassword}
onChange={e => {
setConfirmPassword(e.target.value)