angular/angular · warning
Required "translations" property missing.
Error message
Required "translations" property missing.
What it means
The parsed JSON object is missing the `translations` property. Because the earlier precheck only looked for the substring `"translations"` in the raw text, the word can appear (nested or in a value) while the required top-level key is absent, which is exactly what triggers this warning and a canParse=false verdict.
Source
Thrown at packages/localize/tools/src/translate/translation_files/translation_parsers/simple_json_translation_parser.ts:59
if (
extname(filePath) !== '.json' ||
!(contents.includes('"locale"') && contents.includes('"translations"'))
) {
diagnostics.warn('File does not have .json extension.');
return {canParse: false, diagnostics};
}
try {
const json = JSON.parse(contents) as SimpleJsonFile;
if (json.locale === undefined) {
diagnostics.warn('Required "locale" property missing.');
return {canParse: false, diagnostics};
}
if (typeof json.locale !== 'string') {
diagnostics.warn('The "locale" property is not a string.');
return {canParse: false, diagnostics};
}
if (json.translations === undefined) {
diagnostics.warn('Required "translations" property missing.');
return {canParse: false, diagnostics};
}
if (typeof json.translations !== 'object') {
diagnostics.warn('The "translations" is not an object.');
return {canParse: false, diagnostics};
}
return {canParse: true, diagnostics, hint: json};
} catch (e) {
diagnostics.warn('File is not valid JSON.');
return {canParse: false, diagnostics};
}
}
parse(_filePath: string, contents: string, json?: SimpleJsonFile): ParsedTranslationBundle {
const {locale: parsedLocale, translations} = json || (JSON.parse(contents) as SimpleJsonFile);
const parsedTranslations: Record<MessageId, ɵParsedTranslation> = {};
for (const messageId in translations) {
const targetMessage = translations[messageId];View on GitHub (pinned to 51cb07e980)
Solutions
- Rename the message map key to `translations` so it sits at the top level.
- If the file intentionally uses another key, transform it when generating the file rather than renaming the extension.
- Validate the schema (`locale` string + `translations` object at top level) before invoking the translate step.
Example fix
// before
{ "locale": "fr", "messages": { "hello": "Bonjour" } }
// after
{ "locale": "fr", "translations": { "hello": "Bonjour" } } Defensive patterns
Strategy: type-guard
Validate before calling
const json: unknown = JSON.parse(readFileSync(path, 'utf8'));
if (!isSimpleJsonFile(json)) throw new Error(`${path}: top-level 'translations' object is required`); Type guard
function hasTranslations(v: unknown): v is {translations: Record<string, unknown>} {
return typeof v === 'object' && v !== null && 'translations' in v
&& typeof (v as {translations: unknown}).translations === 'object';
} Prevention
- Standardize on the key name `translations` in every generated file.
- Add a schema assertion so renamed keys (messages/strings/dict) fail fast at generation time.
When it happens
Trigger: A `.json` file with a top-level `locale` but translations nested under another key, e.g. `{ "locale": "fr", "messages": { ... } }` - the substring `"translations"` may occur in a comment-ish value while the key itself is absent.
Common situations: Generators that name the map `strings`, `messages`, or `dict` instead of `translations`; partial hand-written files.
Related errors
- Required "locale" property missing.
- The "translations" is not an object.
- File does not have .json extension.
- The "locale" property is not a string.
- File is not valid JSON.
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/7f4967d892c04a1b.
Report an issue: GitHub.