5e1b39db03
Раньше для записей с x-localized name (КА, антенна, ground_station,
operator) колонка 'name / code' показывала literal '[object Object]'
потому что String({'ru-RU': 'Канопус', 'en-US': 'Kanopus'}) =
toString() стандартный → '[object Object]'.
Решение:
- Новый shared util lib/locales.ts:
- pickLocalized(value, defaultLocale) — извлекает строку из i18n
объекта или plain string. Fallback на first non-empty key
если defaultLocale не найден.
- recordDisplayName(data, defaultLocale) — name → code → '—' chain.
- Records list использует recordDisplayName с dict.defaultLocale
(берётся из DictionaryDetail).
- SchemaDrivenForm дедуплицирован — был свой локальный pickLocalized,
теперь импортирует из util'ы.
Tests: +13 (76 → 89), unit tests на pickLocalized + recordDisplayName
покрывают все edge cases (plain string, localized object, fallback
chain, null/undefined input).
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
/**
|
||
* Извлекает строковое значение из i18n-объекта `{ "ru-RU": "...", "en-US": "..." }`.
|
||
* Schema marker — `x-localized: true`. Backend хранит как JSONB-объект, UI
|
||
* рендерит на основе текущего locale пользователя.
|
||
*
|
||
* Логика выбора:
|
||
* 1. value уже string → return as-is (legacy / non-localized fallback).
|
||
* 2. value object → key === defaultLocale если есть и не пустой.
|
||
* 3. иначе первая non-empty value по любому ключу (стабильный порядок
|
||
* через Object.keys итерацию).
|
||
* 4. nothing → undefined.
|
||
*
|
||
* Возвращает undefined вместо пустой строки чтобы caller мог показать `'—'` /
|
||
* placeholder, а не пустое поле.
|
||
*/
|
||
export const pickLocalized = (
|
||
value: unknown,
|
||
defaultLocale: string,
|
||
): string | undefined => {
|
||
if (typeof value === 'string') return value.length > 0 ? value : undefined
|
||
if (!value || typeof value !== 'object') return undefined
|
||
const map = value as Record<string, unknown>
|
||
const direct = map[defaultLocale]
|
||
if (typeof direct === 'string' && direct.length > 0) return direct
|
||
for (const key of Object.keys(map)) {
|
||
const v = map[key]
|
||
if (typeof v === 'string' && v.length > 0) return v
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
/**
|
||
* Display value для record name column. Tries:
|
||
* 1. localized name (i18n-object)
|
||
* 2. plain `name` string (non-localized dicts)
|
||
* 3. `code` field
|
||
* 4. fallback '—'
|
||
*/
|
||
export const recordDisplayName = (
|
||
data: Record<string, unknown> | undefined,
|
||
defaultLocale: string,
|
||
): string => {
|
||
if (!data) return '—'
|
||
const localized = pickLocalized(data.name, defaultLocale)
|
||
if (localized) return localized
|
||
const code = data.code
|
||
if (typeof code === 'string' && code.length > 0) return code
|
||
return '—'
|
||
}
|