/** * Извлекает строковое значение из 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 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 | 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 '—' }