Короткий ответ: TypeScript и frontend-приложение обычно не читают .resx напрямую. RESX — это формат ресурсов из .NET-экосистемы. Если React, Vue или обычный TypeScript-код должен использовать строки из RESX, практичная схема такая: .resx остается на сервере или в build-процессе, а frontend получает данные как JSON или через API
Типовая архитектура:
.resx -> .NET/backend/build script -> JSON/API -> TypeScript frontend
Так TypeScript работает с понятным объектом, а не пытается парсить XML-ресурс в браузере
Почему не стоит читать RESX напрямую
Файл .resx по сути хранит ресурсы в XML-формате. В .NET он используется для локализации и строковых ресурсов. Браузерному TypeScript-коду это неудобно по нескольким причинам:
- формат не предназначен как frontend-словарь;
- XML нужно парсить и валидировать;
- ресурсы часто собираются на стороне .NET;
- frontend обычно ожидает JSON;
- в production не хочется отдавать внутренние resource-файлы как публичные статические файлы.
Поэтому задача формулируется не как "прочитать RESX в TypeScript", а как "подготовить данные из RESX в формате, удобном для TypeScript"
Вариант 1: экспортировать в JSON
Например, на выходе получить:
{
"save": "Сохранить",
"cancel": "Отмена",
"profile.title": "Профиль"
}
type Messages = Record<string, string>;
const ru: Messages = {
save: "Сохранить",
cancel: "Отмена",
"profile.title": "Профиль",
};
function t(key: keyof typeof ru): string {
return ru[key];
}
Использование:
console.log(t("save"));
Если словарь известен на этапе сборки, можно сделать тип ключей через keyof typeof ru
Вариант 2: получать ресурсы через API
Backend может отдавать локализацию:
GET /api/i18n/ru
Ответ:
{
"save": "Сохранить",
"cancel": "Отмена"
}
Типизация:
type LocaleCode = "ru" | "en";
type Dictionary = Record<string, string>;
async function loadDictionary(locale: LocaleCode): Promise<Dictionary> {
const response = await fetch(`/api/i18n/${locale}`);
if (!response.ok) {
throw new Error(`Failed to load locale: ${locale}`);
}
return response.json() as Promise<Dictionary>;
}
Так frontend не знает, были ли данные взяты из RESX, базы или другого источника
Вариант 3: строгие ключи
Если хотите, чтобы TypeScript подсвечивал неверные ключи:
const messages = {
save: "Сохранить",
cancel: "Отмена",
profileTitle: "Профиль",
} as const;
type MessageKey = keyof typeof messages;
function t(key: MessageKey): string {
return messages[key];
}
Теперь t("unknown") вызовет ошибку TypeScript
Что делать с несколькими языками
type LocaleCode = "ru" | "en";
type MessageKey = "save" | "cancel" | "profileTitle";
type LocaleDictionary = Record<MessageKey, string>;
const dictionaries: Record<LocaleCode, LocaleDictionary> = {
ru: {
save: "Сохранить",
cancel: "Отмена",
profileTitle: "Профиль",
},
en: {
save: "Save",
cancel: "Cancel",
profileTitle: "Profile",
},
};
Так TypeScript проверит, что в каждом языке есть одинаковый набор ключей
Частые ошибки
Первая ошибка — пытаться импортировать .resx как обычный TypeScript-модуль
Вторая ошибка — отдавать frontend внутренний .resx без обработки
Третья ошибка — использовать Record<string, string> везде и не проверять обязательные ключи
Четвертая ошибка — смешивать локализацию frontend и backend без единой схемы ключей
Пятая ошибка — забывать, что переводы могут отсутствовать. Нужен fallback: например, русский ключ, английский ключ или техническая строка
Самопроверка
Сделайте JSON-словарь из трех ключей, опишите MessageKey, создайте Record<MessageKey, string> для двух языков и специально удалите один ключ в английском словаре. TypeScript должен показать ошибку
Что почитать дальше по TypeScript
Если нужен общий маршрут по теме, откройте рубрику TypeScript. Для соседних задач пригодятся эти разборы:
- TS types: как работать с типами в TypeScript
- 10 вопросов по TypeScript и ответы на них
- Any, unknown, never и strict
- App.tsx: что это за файл и как добавить TypeScript в React



