fix(frontend): localize avatar validation errors
checkAvatarFile returned hard-coded Russian strings, so an English user picking a bad file got "Подойдёт PNG, JPG или WebP." with no translation. Route both messages through the account namespace (validation.avatarType / avatarTooBig) the same way the auth validators take a t function, keeping ru/en key parity enforced by the satisfies clause.
This commit is contained in:
parent
fd0fe59fee
commit
07b3f55bee
5 changed files with 18 additions and 7 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { api } from '../../shared/api/client'
|
||||
import type { TFunction } from 'i18next'
|
||||
|
||||
export type DeviceLink = { id: string; linked_at: string; last_seen: string | null }
|
||||
|
||||
|
|
@ -6,9 +7,9 @@ const ALLOWED_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp'])
|
|||
const MAX_BYTES = 5 * 1024 * 1024
|
||||
|
||||
/** Client-side avatar checks so a bad file never reaches the server. */
|
||||
export function checkAvatarFile(file: File): string | undefined {
|
||||
if (!ALLOWED_TYPES.has(file.type)) return 'Подойдёт PNG, JPG или WebP.'
|
||||
if (file.size > MAX_BYTES) return 'Файл больше 5 МБ.'
|
||||
export function checkAvatarFile(file: File, t: TFunction<'account'>): string | undefined {
|
||||
if (!ALLOWED_TYPES.has(file.type)) return t('validation.avatarType')
|
||||
if (file.size > MAX_BYTES) return t('validation.avatarTooBig')
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function AvatarUpload() {
|
|||
const file = e.target.files?.[0]
|
||||
e.target.value = ''
|
||||
if (!file) return
|
||||
const problem = checkAvatarFile(file)
|
||||
const problem = checkAvatarFile(file, t)
|
||||
if (problem) {
|
||||
setError(problem)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ export const accountEn = {
|
|||
avatar: {
|
||||
change: 'Change avatar',
|
||||
},
|
||||
validation: {
|
||||
avatarType: 'PNG, JPG or WebP please.',
|
||||
avatarTooBig: 'The file is over 5 MB.',
|
||||
},
|
||||
devices: {
|
||||
heading: 'Linked games',
|
||||
linkedAt: 'Linked {{date}}',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ export const accountRu = {
|
|||
avatar: {
|
||||
change: 'Сменить аватар',
|
||||
},
|
||||
validation: {
|
||||
avatarType: 'Подойдёт PNG, JPG или WebP.',
|
||||
avatarTooBig: 'Файл больше 5 МБ.',
|
||||
},
|
||||
devices: {
|
||||
heading: 'Привязанные игры',
|
||||
linkedAt: 'Привязано {{date}}',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { expect, test } from 'vitest'
|
||||
import { checkAvatarFile } from '../api'
|
||||
import { DeviceList } from '../components/DeviceList'
|
||||
|
|
@ -7,10 +8,11 @@ import { renderApp } from '../../../test/render'
|
|||
import { json, mockFetch } from '../../../test/fetch'
|
||||
|
||||
test('avatar file checks', () => {
|
||||
expect(checkAvatarFile(new File(['x'], 'a.gif', { type: 'image/gif' }))).toBe('Подойдёт PNG, JPG или WebP.')
|
||||
const t = getI18n().getFixedT('ru', 'account')
|
||||
expect(checkAvatarFile(new File(['x'], 'a.gif', { type: 'image/gif' }), t)).toBe('Подойдёт PNG, JPG или WebP.')
|
||||
const big = new File([new Uint8Array(5 * 1024 * 1024 + 1)], 'a.png', { type: 'image/png' })
|
||||
expect(checkAvatarFile(big)).toBe('Файл больше 5 МБ.')
|
||||
expect(checkAvatarFile(new File(['x'], 'a.png', { type: 'image/png' }))).toBeUndefined()
|
||||
expect(checkAvatarFile(big, t)).toBe('Файл больше 5 МБ.')
|
||||
expect(checkAvatarFile(new File(['x'], 'a.png', { type: 'image/png' }), t)).toBeUndefined()
|
||||
})
|
||||
|
||||
test('revoking a device asks for confirmation first', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue