refactor(localize): replace any with unknown in messages and translations utils

Replace unsafe `any` type annotations with `unknown` across the localize
utility layer to improve type safety and catch potential type errors at
compile time rather than at runtime.

Changes in `messages.ts`:
- `ParsedMessage.substitutions`: `Record<string, any>` → `Record<string, unknown>`
- `parseMessage` parameter `expressions`: `readonly any[]` → `readonly unknown[]`
- Local `substitutions` variable: `{[key: string]: any}` → `Record<string, unknown>`

Changes in `translations.ts`:
- `isMissingTranslationError` parameter: `any` → `unknown`, with proper
  narrowing (`typeof e === 'object' && e !== null`) before property access
- `MissingTranslationError.type` visibility: `private` → `readonly` to allow
  access through the narrowed `unknown` type in the type guard
- `translate` parameter and return type: `readonly any[]` → `readonly unknown[]`
- `makeTemplateObject` cast: `cooked as any` → `cooked as unknown as TemplateStringsArray`

Fix in `mock_message.ts` (test helper):
- `substitutions: []` → `substitutions: {}` — the array literal was only
  assignable because the field was typed as `any`; the correct empty value
  for a `Record<string, unknown>` is an object literal
This commit is contained in:
splincode
2026-03-31 15:00:34 +03:00
committed by Jessica Janiuk
parent 38d093232c
commit b10021c79b
3 changed files with 14 additions and 10 deletions
+3 -3
View File
@@ -138,7 +138,7 @@ export interface ParsedMessage extends MessageMetadata {
/**
* A mapping of placeholder names to substitution values.
*/
substitutions: Record<string, any>;
substitutions: Record<string, unknown>;
/**
* An optional mapping of placeholder names to associated MessageIds.
* This can be used to match ICU placeholders to the message that contains the ICU.
@@ -170,12 +170,12 @@ export interface ParsedMessage extends MessageMetadata {
*/
export function parseMessage(
messageParts: TemplateStringsArray,
expressions?: readonly any[],
expressions?: readonly unknown[],
location?: SourceLocation,
messagePartLocations?: (SourceLocation | undefined)[],
expressionLocations: (SourceLocation | undefined)[] = [],
): ParsedMessage {
const substitutions: {[placeholderName: string]: any} = {};
const substitutions: Record<string, unknown> = {};
const substitutionLocations: {[placeholderName: string]: SourceLocation | undefined} = {};
const associatedMessageIds: {[placeholderName: string]: MessageId} = {};
const metadata = parseMetadata(messageParts[0], messageParts.raw[0]);
@@ -22,14 +22,18 @@ export interface ParsedTranslation extends MessageMetadata {
export type ParsedTranslations = Record<MessageId, ParsedTranslation>;
export class MissingTranslationError extends Error {
private readonly type = 'MissingTranslationError';
readonly type = 'MissingTranslationError';
constructor(readonly parsedMessage: ParsedMessage) {
super(`No translation found for ${describeMessage(parsedMessage)}.`);
}
}
export function isMissingTranslationError(e: any): e is MissingTranslationError {
return e.type === 'MissingTranslationError';
export function isMissingTranslationError(e: unknown): e is MissingTranslationError {
return (
typeof e === 'object' &&
e !== null &&
(e as MissingTranslationError).type === 'MissingTranslationError'
);
}
/**
@@ -51,8 +55,8 @@ export function isMissingTranslationError(e: any): e is MissingTranslationError
export function translate(
translations: Record<string, ParsedTranslation>,
messageParts: TemplateStringsArray,
substitutions: readonly any[],
): [TemplateStringsArray, readonly any[]] {
substitutions: readonly unknown[],
): [TemplateStringsArray, readonly unknown[]] {
const message = parseMessage(messageParts, substitutions);
// Look up the translation using the messageId, and then the legacyId if available.
let translation = translations[message.id];
@@ -137,7 +141,7 @@ export function makeParsedTranslation(
*/
export function makeTemplateObject(cooked: string[], raw: string[]): TemplateStringsArray {
Object.defineProperty(cooked, 'raw', {value: raw});
return cooked as any;
return cooked as unknown as TemplateStringsArray;
}
function describeMessage(message: ParsedMessage): string {
@@ -35,7 +35,7 @@ export function mockMessage(
text += `{$${placeholderNames[i - 1]}}${messageParts[i]}`;
}
return {
substitutions: [],
substitutions: {},
...options,
id: options.customId || id, // customId trumps id
text,