-
Field Data
+
{t('documents.fieldData')}
([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
@@ -15,7 +17,7 @@ export default function DocumentsPage() {
const load = () => {
api.listDocuments()
.then(setDocuments)
- .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load'))
+ .catch((err) => setError(err instanceof Error ? err.message : t('errors.loadFailed')))
.finally(() => setLoading(false))
}
@@ -30,7 +32,7 @@ export default function DocumentsPage() {
setDocuments((list) => list.filter((d) => d.id !== deleteTarget.id))
setDeleteTarget(null)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Delete failed')
+ alert(err instanceof Error ? err.message : t('errors.deleteFailed'))
}
}
@@ -40,43 +42,46 @@ export default function DocumentsPage() {
void handleDelete()}
onCancel={() => setDeleteTarget(null)}
/>
-
Documents
+ {t('documents.title')}
- Create from Template
+ {t('documents.createFromTemplate')}
{error && {error}
}
{loading ? (
- Loading...
+ {t('common.loading')}
) : documents.length === 0 ? (
-
No filled documents yet. Choose a template and fill in the variables.
+
{t('documents.empty')}
) : (
- | Name |
- {isAdmin && Owner | }
- Template ID |
- Created |
- Actions |
+ {t('common.name')} |
+ {isAdmin && {t('common.owner')} | }
+ {t('documents.templateId')} |
+ {t('common.created')} |
+ {t('common.actions')} |
@@ -85,20 +90,20 @@ export default function DocumentsPage() {
{d.name} |
{isAdmin && {d.owner_username ?? `#${d.owner_id}`} | }
{d.template_id} |
- {new Date(d.created_at).toLocaleString()} |
+ {formatDateTime(d.created_at)} |
- View
+ {t('common.view')}
- Edit
+ {t('common.edit')}
{(isAdmin || d.owner_id === user?.id) && (
)}
|
diff --git a/frontend/src/pages/EditDocumentPage.tsx b/frontend/src/pages/EditDocumentPage.tsx
index 8bdc234..6b8598d 100644
--- a/frontend/src/pages/EditDocumentPage.tsx
+++ b/frontend/src/pages/EditDocumentPage.tsx
@@ -3,11 +3,13 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
import { api } from '../api'
import TableRowEditor from '../components/TableRowEditor'
import VariableField from '../components/VariableField'
+import { useI18n } from '../context/I18nContext'
import type { DocumentTemplate } from '../types'
export default function EditDocumentPage() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
+ const { t } = useI18n()
const [template, setTemplate] = useState(null)
const [fieldData, setFieldData] = useState>({})
const [documentName, setDocumentName] = useState('')
@@ -21,12 +23,12 @@ export default function EditDocumentPage() {
const load = async () => {
try {
const doc = await api.getDocument(Number(id))
- const t = await api.getTemplate(doc.template_id)
- setTemplate(t)
+ const loadedTemplate = await api.getTemplate(doc.template_id)
+ setTemplate(loadedTemplate)
setDocumentName(doc.name)
setFieldData(doc.field_data)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load document')
+ setError(err instanceof Error ? err.message : t('errors.loadDocumentFailed'))
} finally {
setLoading(false)
}
@@ -64,7 +66,7 @@ export default function EditDocumentPage() {
setPreviewHtml(result.html)
setStep(3)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Preview failed')
+ setError(err instanceof Error ? err.message : t('errors.previewFailed'))
} finally {
setSubmitting(false)
}
@@ -81,23 +83,23 @@ export default function EditDocumentPage() {
})
navigate(`/documents/${doc.id}`)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Save failed')
+ setError(err instanceof Error ? err.message : t('errors.saveFailed'))
} finally {
setSubmitting(false)
}
}
- if (loading) return
- if (!template) return
+ if (loading) return
+ if (!template) return {error || t('common.notFound')}
return (
-
Edit Document
-
Template: {template.name}
+
{t('documents.editTitle')}
+
{t('documents.templateLine', { name: template.name })}
-
Back
+
{t('common.back')}
{error &&
{error}
}
@@ -105,7 +107,7 @@ export default function EditDocumentPage() {
{step === 2 && (
@@ -162,14 +164,14 @@ export default function EditDocumentPage() {
diff --git a/frontend/src/pages/FillTemplatePage.tsx b/frontend/src/pages/FillTemplatePage.tsx
index dce971a..def6735 100644
--- a/frontend/src/pages/FillTemplatePage.tsx
+++ b/frontend/src/pages/FillTemplatePage.tsx
@@ -3,11 +3,13 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
import { api } from '../api'
import TableRowEditor from '../components/TableRowEditor'
import VariableField from '../components/VariableField'
+import { useI18n } from '../context/I18nContext'
import type { DocumentTemplate } from '../types'
export default function FillTemplatePage() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
+ const { t } = useI18n()
const [template, setTemplate] = useState(null)
const [fieldData, setFieldData] = useState>({})
const [documentName, setDocumentName] = useState('')
@@ -20,12 +22,12 @@ export default function FillTemplatePage() {
useEffect(() => {
const load = async () => {
try {
- const t = await api.getTemplate(Number(id))
- setTemplate(t)
- setDocumentName(`${t.name} - ${new Date().toLocaleDateString()}`)
+ const loaded = await api.getTemplate(Number(id))
+ setTemplate(loaded)
+ setDocumentName(`${loaded.name} - ${new Date().toLocaleDateString()}`)
const initial: Record = {}
- t.variables.forEach((v) => {
+ loaded.variables.forEach((v) => {
if (v.field_type === 'table_row') {
initial[v.name] = []
} else if (!v.parent_variable) {
@@ -34,7 +36,7 @@ export default function FillTemplatePage() {
})
setFieldData(initial)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load template')
+ setError(err instanceof Error ? err.message : t('errors.loadTemplateFailed'))
} finally {
setLoading(false)
}
@@ -72,7 +74,7 @@ export default function FillTemplatePage() {
setPreviewHtml(result.html)
setStep(3)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Preview failed')
+ setError(err instanceof Error ? err.message : t('errors.previewFailed'))
} finally {
setSubmitting(false)
}
@@ -90,29 +92,31 @@ export default function FillTemplatePage() {
})
navigate(`/documents/${doc.id}`)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Save failed')
+ setError(err instanceof Error ? err.message : t('errors.saveFailed'))
} finally {
setSubmitting(false)
}
}
- if (loading) return
- if (!template) return
+ if (loading) return
+ if (!template) return {error || t('common.notFound')}
return (
-
Fill: {template.name}
-
Reusable template · {template.variables.length} variables detected
+
{t('templates.fillTitle', { name: template.name })}
+
+ {t('templates.fillHint', { count: template.variables.length })}
+
-
Back
+
{t('common.back')}
-
1. Upload .docx
-
2 ? 'done' : ''}`}>2. Fill Variables
-
3. Preview & Export
+
{t('templates.stepUpload')}
+
2 ? 'done' : ''}`}>{t('templates.stepFill')}
+
{t('templates.stepPreview')}
{error &&
{error}
}
@@ -120,10 +124,10 @@ export default function FillTemplatePage() {
{step === 2 && (
-
Document Fields
+
{t('templates.documentFields')}
-
+
setDocumentName(e.target.value)}
@@ -164,22 +168,22 @@ export default function FillTemplatePage() {
onClick={handlePreview}
disabled={submitting}
>
- {submitting ? 'Rendering...' : 'Preview Document'}
+ {submitting ? t('templates.rendering') : t('templates.previewDocument')}
-
Template Info
-
File: {template.original_filename}
+
{t('templates.templateInfo')}
+
{t('common.file')}: {template.original_filename}
{template.description &&
{template.description}
}
-
Detected Variables
+
{t('templates.detectedVariables')}
- | Name |
- Type |
- Style |
+ {t('common.name')} |
+ {t('templates.type')} |
+ {t('templates.style')} |
@@ -188,7 +192,7 @@ export default function FillTemplatePage() {
{v.name} |
{v.field_type} |
- {v.style_params?.font_name ?? '—'}
+ {v.style_params?.font_name ?? t('common.empty')}
{v.style_params?.alignment ? ` / ${v.style_params.alignment}` : ''}
|
@@ -209,14 +213,14 @@ export default function FillTemplatePage() {
diff --git a/frontend/src/pages/ForgotPasswordPage.tsx b/frontend/src/pages/ForgotPasswordPage.tsx
index fb5df69..7878d59 100644
--- a/frontend/src/pages/ForgotPasswordPage.tsx
+++ b/frontend/src/pages/ForgotPasswordPage.tsx
@@ -1,8 +1,11 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../api'
+import AuthShell from '../components/AuthShell'
+import { useI18n } from '../context/I18nContext'
export default function ForgotPasswordPage() {
+ const { t } = useI18n()
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
@@ -17,24 +20,24 @@ export default function ForgotPasswordPage() {
const result = await api.forgotPassword(email)
setSuccess(result.message)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Request failed')
+ setError(err instanceof Error ? err.message : t('errors.requestFailed'))
} finally {
setLoading(false)
}
}
return (
-
+
-
Forgot Password
-
Enter your email and we will send a reset code.
+
{t('auth.forgotTitle')}
+
{t('auth.forgotHint')}
{error &&
{error}
}
{success &&
{success}
}
- Have a code? Reset password
+ {t('auth.haveCode')} {t('auth.resetPasswordLink')}
- Back to login
+ {t('auth.backToLogin')}
-
+
)
}
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
index 8257379..8fcf0c7 100644
--- a/frontend/src/pages/LoginPage.tsx
+++ b/frontend/src/pages/LoginPage.tsx
@@ -1,9 +1,12 @@
import { useState } from 'react'
import { Link, Navigate } from 'react-router-dom'
+import AuthShell from '../components/AuthShell'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
export default function LoginPage() {
const { user, login } = useAuth()
+ const { t } = useI18n()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
@@ -18,23 +21,23 @@ export default function LoginPage() {
try {
await login(username, password)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Login failed')
+ setError(err instanceof Error ? err.message : t('errors.loginFailed'))
} finally {
setLoading(false)
}
}
return (
-
+
-
Document Template Editor
-
Upload Jinja2 .docx templates, fill variables, preview and export.
+
{t('nav.brand')}
+
{t('auth.tagline')}
{error &&
{error}
}
- Create account
+ {t('auth.createAccount')}
- Activate account
+ {t('auth.activateAccount')}
- Forgot password?
+ {t('auth.forgotPassword')}
-
+
)
}
diff --git a/frontend/src/pages/LogsPage.tsx b/frontend/src/pages/LogsPage.tsx
index df0cb66..9648f6f 100644
--- a/frontend/src/pages/LogsPage.tsx
+++ b/frontend/src/pages/LogsPage.tsx
@@ -2,20 +2,25 @@ import { useEffect, useState } from 'react'
import { Navigate } from 'react-router-dom'
import { api } from '../api'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
import type { AuditLog } from '../types'
function formatAction(action: string): string {
return action.replace(/\./g, ' · ').replace(/_/g, ' ')
}
-function formatDetails(details: Record | null): string {
- if (!details) return '—'
+function formatDetails(
+ details: Record | null,
+ emptyLabel: string,
+): string {
+ if (!details) return emptyLabel
const text = JSON.stringify(details, null, 0)
return text.length > 120 ? `${text.slice(0, 120)}…` : text
}
export default function LogsPage() {
const { user: currentUser } = useAuth()
+ const { t, formatDateTime } = useI18n()
const [logs, setLogs] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
@@ -27,7 +32,7 @@ export default function LogsPage() {
try {
setLogs(await api.listLogs({ action: action || undefined }))
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load logs')
+ setError(err instanceof Error ? err.message : t('errors.loadLogsFailed'))
} finally {
setLoading(false)
}
@@ -49,21 +54,21 @@ export default function LogsPage() {
return (
-
Activity Logs
+ {t('logs.title')}
@@ -81,48 +86,48 @@ export default function LogsPage() {
{error &&
{error}
}
{loading ? (
-
Loading...
+
{t('common.loading')}
) : (
- | Time |
- User |
- Action |
- Resource |
- Details |
- IP |
+ {t('logs.time')} |
+ {t('logs.user')} |
+ {t('logs.action')} |
+ {t('logs.resource')} |
+ {t('logs.details')} |
+ {t('logs.ip')} |
{logs.length === 0 ? (
|
- No log entries found
+ {t('logs.empty')}
|
) : (
logs.map((log) => (
|
- {new Date(log.created_at).toLocaleString()}
+ {formatDateTime(log.created_at)}
|
- {log.username || '—'} |
+ {log.username || t('common.empty')} |
{formatAction(log.action)}
|
{log.resource_type
? `${log.resource_type}${log.resource_id != null ? ` #${log.resource_id}` : ''}`
- : '—'}
+ : t('common.empty')}
|
- {formatDetails(log.details)}
+ {formatDetails(log.details, t('common.empty'))}
|
- {log.ip_address || '—'} |
+ {log.ip_address || t('common.empty')} |
))
)}
diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx
index 72b946d..c089fa3 100644
--- a/frontend/src/pages/RegisterPage.tsx
+++ b/frontend/src/pages/RegisterPage.tsx
@@ -1,10 +1,13 @@
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
+import AuthShell from '../components/AuthShell'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
export default function RegisterPage() {
const { register } = useAuth()
const navigate = useNavigate()
+ const { t } = useI18n()
const [email, setEmail] = useState('')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
@@ -19,7 +22,7 @@ export default function RegisterPage() {
setSuccess('')
if (password !== confirmPassword) {
- setError('Passwords do not match')
+ setError(t('errors.passwordsMismatch'))
return
}
@@ -31,24 +34,24 @@ export default function RegisterPage() {
navigate('/activate', { state: { email: result.email } })
}, 1500)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Registration failed')
+ setError(err instanceof Error ? err.message : t('errors.registrationFailed'))
} finally {
setLoading(false)
}
}
return (
-
+
-
Create Account
-
Register to use document templates. You will receive an activation code by email.
+
{t('auth.registerTitle')}
+
{t('auth.registerHint')}
{error &&
{error}
}
{success &&
{success}
}
- Already have an account? Login
+ {t('auth.alreadyHaveAccount')} {t('auth.login')}
-
+
)
}
diff --git a/frontend/src/pages/ResetPasswordPage.tsx b/frontend/src/pages/ResetPasswordPage.tsx
index ed2dc6b..eb43b37 100644
--- a/frontend/src/pages/ResetPasswordPage.tsx
+++ b/frontend/src/pages/ResetPasswordPage.tsx
@@ -1,9 +1,12 @@
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { api } from '../api'
+import AuthShell from '../components/AuthShell'
+import { useI18n } from '../context/I18nContext'
export default function ResetPasswordPage() {
const navigate = useNavigate()
+ const { t } = useI18n()
const [email, setEmail] = useState('')
const [code, setCode] = useState('')
const [password, setPassword] = useState('')
@@ -18,7 +21,7 @@ export default function ResetPasswordPage() {
setSuccess('')
if (password !== confirmPassword) {
- setError('Passwords do not match')
+ setError(t('errors.passwordsMismatch'))
return
}
@@ -33,24 +36,24 @@ export default function ResetPasswordPage() {
setSuccess(result.message)
setTimeout(() => navigate('/login'), 1500)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Reset failed')
+ setError(err instanceof Error ? err.message : t('errors.resetFailed'))
} finally {
setLoading(false)
}
}
return (
-
+
-
Reset Password
-
Enter the code from your email and choose a new password.
+
{t('auth.resetTitle')}
+
{t('auth.resetHint')}
{error &&
{error}
}
{success &&
{success}
}
- Back to login
+ {t('auth.backToLogin')}
-
+
)
}
diff --git a/frontend/src/pages/TemplateDetailPage.tsx b/frontend/src/pages/TemplateDetailPage.tsx
index 51207e0..dbafc9f 100644
--- a/frontend/src/pages/TemplateDetailPage.tsx
+++ b/frontend/src/pages/TemplateDetailPage.tsx
@@ -2,11 +2,13 @@ import { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { api, downloadWithAuth } from '../api'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
import type { DocumentTemplate } from '../types'
export default function TemplateDetailPage() {
const { id } = useParams<{ id: string }>()
const { user } = useAuth()
+ const { t, formatDateTime } = useI18n()
const [template, setTemplate] = useState(null)
const [error, setError] = useState('')
const [loading, setLoading] = useState(true)
@@ -14,7 +16,7 @@ export default function TemplateDetailPage() {
useEffect(() => {
api.getTemplate(Number(id))
.then(setTemplate)
- .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load'))
+ .catch((err) => setError(err instanceof Error ? err.message : t('errors.loadFailed')))
.finally(() => setLoading(false))
}, [id])
@@ -26,7 +28,7 @@ export default function TemplateDetailPage() {
})
setTemplate(updated)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Update failed')
+ alert(err instanceof Error ? err.message : t('errors.updateFailed'))
}
}
@@ -38,11 +40,11 @@ export default function TemplateDetailPage() {
template.original_filename,
)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Download failed')
+ alert(err instanceof Error ? err.message : t('errors.downloadFailed'))
}
}
- if (loading) return
+ if (loading) return
if (!template) return
const canManage = template.is_owner || user?.role === 'admin'
@@ -53,38 +55,38 @@ export default function TemplateDetailPage() {
{template.name}
- Use Template
+ {t('templates.useTemplate')}
{canManage && (
)}
-
File: {template.original_filename}
-
Owner: {template.owner_username ?? `#${template.owner_id}`}
+
{t('common.file')}: {template.original_filename}
+
{t('common.owner')}: {template.owner_username ?? `#${template.owner_id}`}
- Visibility:{' '}
- {template.is_public ? 'Public (visible to all users)' : 'Private'}
+ {t('templates.visibility')}:{' '}
+ {template.is_public ? t('visibility.publicFull') : t('visibility.privateFull')}
{template.description &&
{template.description}
}
-
Created: {new Date(template.created_at).toLocaleString()}
+
{t('common.created')}: {formatDateTime(template.created_at)}
-
Variables ({template.variables.length})
+
{t('templates.variablesCount', { count: template.variables.length })}
- | Label |
- Name |
- Type |
- Required |
- Style Parameters |
+ {t('templates.label')} |
+ {t('common.name')} |
+ {t('templates.type')} |
+ {t('templates.required')} |
+ {t('templates.styleParams')} |
@@ -93,11 +95,11 @@ export default function TemplateDetailPage() {
{v.label} |
{v.name} |
{v.field_type} |
- {v.is_required ? 'Yes' : 'No'} |
+ {v.is_required ? t('common.yes') : t('common.no')} |
{v.style_params
? JSON.stringify(v.style_params, null, 0).slice(0, 80) + '...'
- : '—'}
+ : t('common.empty')}
|
))}
diff --git a/frontend/src/pages/TemplatesPage.tsx b/frontend/src/pages/TemplatesPage.tsx
index acddc82..d420837 100644
--- a/frontend/src/pages/TemplatesPage.tsx
+++ b/frontend/src/pages/TemplatesPage.tsx
@@ -3,10 +3,12 @@ import { Link } from 'react-router-dom'
import { api, downloadWithAuth } from '../api'
import ConfirmDialog from '../components/ConfirmDialog'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
import type { TemplateListItem } from '../types'
export default function TemplatesPage() {
const { user } = useAuth()
+ const { t, formatDate } = useI18n()
const [templates, setTemplates] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
@@ -16,7 +18,7 @@ export default function TemplatesPage() {
try {
setTemplates(await api.listTemplates())
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load templates')
+ setError(err instanceof Error ? err.message : t('errors.loadTemplatesFailed'))
} finally {
setLoading(false)
}
@@ -33,71 +35,69 @@ export default function TemplatesPage() {
setTemplates((list) => list.filter((x) => x.id !== deleteTarget.id))
setDeleteTarget(null)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Delete failed')
+ alert(err instanceof Error ? err.message : t('errors.deleteFailed'))
}
}
- const handleTogglePublic = async (t: TemplateListItem) => {
+ const handleTogglePublic = async (item: TemplateListItem) => {
try {
- const updated = await api.updateTemplate(t.id, { is_public: !t.is_public })
+ const updated = await api.updateTemplate(item.id, { is_public: !item.is_public })
setTemplates((list) =>
list.map((x) =>
- x.id === t.id
- ? { ...x, is_public: updated.is_public }
- : x,
+ x.id === item.id ? { ...x, is_public: updated.is_public } : x,
),
)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Update failed')
+ alert(err instanceof Error ? err.message : t('errors.updateFailed'))
}
}
- const handleDownloadSource = async (t: TemplateListItem) => {
+ const handleDownloadSource = async (item: TemplateListItem) => {
try {
- await downloadWithAuth(
- api.getTemplateSourceUrl(t.id),
- t.original_filename,
- )
+ await downloadWithAuth(api.getTemplateSourceUrl(item.id), item.original_filename)
} catch (err) {
- alert(err instanceof Error ? err.message : 'Download failed')
+ alert(err instanceof Error ? err.message : t('errors.downloadFailed'))
}
}
- const canManage = (t: TemplateListItem) =>
- t.is_owner || user?.role === 'admin'
+ const canManage = (item: TemplateListItem) =>
+ item.is_owner || user?.role === 'admin'
return (
void handleDelete()}
onCancel={() => setDeleteTarget(null)}
/>
-
Templates
+ {t('templates.title')}
- + Upload Template
+ {t('templates.upload')}
{error && {error}
}
{loading ? (
- Loading...
+ {t('common.loading')}
) : templates.length === 0 ? (
-
No templates yet. Upload a .docx file with Jinja2 variables to get started.
+
{t('templates.empty')}
- Upload Template
+ {t('templates.uploadShort')}
) : (
@@ -105,63 +105,63 @@ export default function TemplatesPage() {
- | Name |
- File |
- Owner |
- Visibility |
- Variables |
- Created |
- Actions |
+ {t('common.name')} |
+ {t('common.file')} |
+ {t('common.owner')} |
+ {t('templates.visibility')} |
+ {t('templates.variables')} |
+ {t('common.created')} |
+ {t('common.actions')} |
- {templates.map((t) => (
-
+ {templates.map((item) => (
+
|
- {t.name}
- {t.description && (
- {t.description}
+ {item.name}
+ {item.description && (
+ {item.description}
)}
|
- {t.original_filename} |
- {t.owner_username ?? `#${t.owner_id}`} |
+ {item.original_filename} |
+ {item.owner_username ?? `#${item.owner_id}`} |
- {t.is_public ? (
+ {item.is_public ? (
- public
+ {t('visibility.public')}
) : (
- private
+ {t('visibility.private')}
)}
|
- {t.variable_count} |
- {new Date(t.created_at).toLocaleDateString()} |
+ {item.variable_count} |
+ {formatDate(item.created_at)} |
-
- Fill
+
+ {t('templates.fill')}
-
- View
+
+ {t('common.view')}
- {canManage(t) && (
+ {canManage(item) && (
<>
>
)}
diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx
index 15c0407..6e56943 100644
--- a/frontend/src/pages/UsersPage.tsx
+++ b/frontend/src/pages/UsersPage.tsx
@@ -3,6 +3,7 @@ import { Navigate } from 'react-router-dom'
import { api } from '../api'
import ConfirmDialog from '../components/ConfirmDialog'
import { useAuth } from '../context/AuthContext'
+import { useI18n } from '../context/I18nContext'
import type { User } from '../types'
type PendingConfirm = {
@@ -15,6 +16,7 @@ type PendingConfirm = {
export default function UsersPage() {
const { user: currentUser } = useAuth()
+ const { t, formatDate } = useI18n()
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
@@ -39,7 +41,7 @@ export default function UsersPage() {
try {
setUsers(await api.listUsers())
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load users')
+ setError(err instanceof Error ? err.message : t('errors.loadUsersFailed'))
} finally {
setLoading(false)
}
@@ -58,7 +60,7 @@ export default function UsersPage() {
setShowForm(false)
await load()
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to create user')
+ setError(err instanceof Error ? err.message : t('errors.createUserFailed'))
}
}
@@ -87,7 +89,7 @@ export default function UsersPage() {
setEditingUser(null)
await load()
} catch (err) {
- setError(err instanceof Error ? err.message : 'Update failed')
+ setError(err instanceof Error ? err.message : t('errors.updateFailed'))
}
}
@@ -98,11 +100,11 @@ export default function UsersPage() {
if (editForm.is_active !== editingUser.is_active) {
const activating = editForm.is_active
setPendingConfirm({
- title: activating ? 'Activate user' : 'Deactivate user',
+ title: activating ? t('users.activateTitle') : t('users.deactivateTitle'),
message: activating
- ? `Activate user "${editingUser.username}"? They will be able to sign in again.`
- : `Deactivate user "${editingUser.username}"? They will no longer be able to sign in.`,
- confirmLabel: activating ? 'Activate' : 'Deactivate',
+ ? t('users.activateMessage', { username: editingUser.username })
+ : t('users.deactivateMessage', { username: editingUser.username }),
+ confirmLabel: activating ? t('users.activate') : t('users.deactivate'),
danger: !activating,
onConfirm: submitEdit,
})
@@ -115,11 +117,11 @@ export default function UsersPage() {
const handleToggleActive = (u: User) => {
const activating = !u.is_active
setPendingConfirm({
- title: activating ? 'Activate user' : 'Deactivate user',
+ title: activating ? t('users.activateTitle') : t('users.deactivateTitle'),
message: activating
- ? `Activate user "${u.username}"? They will be able to sign in again.`
- : `Deactivate user "${u.username}"? They will no longer be able to sign in.`,
- confirmLabel: activating ? 'Activate' : 'Deactivate',
+ ? t('users.activateMessage', { username: u.username })
+ : t('users.deactivateMessage', { username: u.username }),
+ confirmLabel: activating ? t('users.activate') : t('users.deactivate'),
danger: !activating,
onConfirm: async () => {
await api.updateUser(u.id, { is_active: !u.is_active })
@@ -130,9 +132,12 @@ export default function UsersPage() {
const handleDelete = (u: User) => {
setPendingConfirm({
- title: 'Delete user',
- message: `Delete user "${u.username}"? This action cannot be undone.`,
- confirmLabel: 'Delete',
+ title: t('users.deleteTitle'),
+ message: t('users.deleteMessage', {
+ username: u.username,
+ cannotUndo: t('common.cannotUndo'),
+ }),
+ confirmLabel: t('common.delete'),
danger: true,
onConfirm: async () => {
await api.deleteUser(u.id)
@@ -148,7 +153,7 @@ export default function UsersPage() {
try {
await action()
} catch (err) {
- alert(err instanceof Error ? err.message : 'Action failed')
+ alert(err instanceof Error ? err.message : t('errors.actionFailed'))
}
}
@@ -169,9 +174,9 @@ export default function UsersPage() {
/>
- User Management
+ {t('users.title')}
@@ -179,10 +184,10 @@ export default function UsersPage() {
{showForm && (
- Create User
+ {t('users.createUser')}
)}
{editingUser && (
- Edit User: {editingUser.username}
+
+ {t('users.editUser', { username: editingUser.username })}
+
@@ -297,18 +304,18 @@ export default function UsersPage() {
)}
{loading ? (
- Loading...
+ {t('common.loading')}
) : (
- | Username |
- Email |
- Role |
- Status |
- Created |
- Actions |
+ {t('common.username')} |
+ {t('common.email')} |
+ {t('common.role')} |
+ {t('users.status')} |
+ {t('common.created')} |
+ {t('common.actions')} |
@@ -317,11 +324,11 @@ export default function UsersPage() {
{u.username}
{u.id === currentUser?.id && (
- (you)
+ {t('common.you')}
)}
|
{u.email} |
- {u.role} |
+ {t(`roles.${u.role}`)} |
- {u.is_active ? 'active' : 'inactive'}
+ {u.is_active ? t('status.active') : t('status.inactive')}
|
- {new Date(u.created_at).toLocaleDateString()} |
+ {formatDate(u.created_at)} |
|
diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo
index 4414d40..a4efda7 100644
--- a/frontend/tsconfig.tsbuildinfo
+++ b/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/App.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/ConfirmDialog.tsx","./src/components/Navbar.tsx","./src/components/TableRowEditor.tsx","./src/components/VariableField.tsx","./src/context/AuthContext.tsx","./src/pages/ActivatePage.tsx","./src/pages/CreateTemplatePage.tsx","./src/pages/DocumentDetailPage.tsx","./src/pages/DocumentsPage.tsx","./src/pages/EditDocumentPage.tsx","./src/pages/FillTemplatePage.tsx","./src/pages/ForgotPasswordPage.tsx","./src/pages/LoginPage.tsx","./src/pages/LogsPage.tsx","./src/pages/RegisterPage.tsx","./src/pages/ResetPasswordPage.tsx","./src/pages/TemplateDetailPage.tsx","./src/pages/TemplatesPage.tsx","./src/pages/UsersPage.tsx"],"version":"5.9.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/AuthShell.tsx","./src/components/ConfirmDialog.tsx","./src/components/LanguageSwitcher.tsx","./src/components/Navbar.tsx","./src/components/TableRowEditor.tsx","./src/components/VariableField.tsx","./src/context/AuthContext.tsx","./src/context/I18nContext.tsx","./src/i18n/index.ts","./src/i18n/locales/en.ts","./src/i18n/locales/ru.ts","./src/pages/ActivatePage.tsx","./src/pages/CreateTemplatePage.tsx","./src/pages/DocumentDetailPage.tsx","./src/pages/DocumentsPage.tsx","./src/pages/EditDocumentPage.tsx","./src/pages/FillTemplatePage.tsx","./src/pages/ForgotPasswordPage.tsx","./src/pages/LoginPage.tsx","./src/pages/LogsPage.tsx","./src/pages/RegisterPage.tsx","./src/pages/ResetPasswordPage.tsx","./src/pages/TemplateDetailPage.tsx","./src/pages/TemplatesPage.tsx","./src/pages/UsersPage.tsx"],"version":"5.9.3"}
\ No newline at end of file
|