CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-intl-messageformat

Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Comprehensive error system for handling various formatting failures including missing values, invalid types, and missing Intl APIs.

Capabilities

ErrorCode Enum

Defines the types of errors that can occur during message formatting.

/**
 * Error codes for different types of formatting failures
 */
enum ErrorCode {
  /** When a placeholder has no corresponding value provided */
  MISSING_VALUE = 'MISSING_VALUE',
  /** When a provided value is invalid for the placeholder type */
  INVALID_VALUE = 'INVALID_VALUE',
  /** When a required Intl API is not available in the environment */
  MISSING_INTL_API = 'MISSING_INTL_API'
}

FormatError Class

Base error class for all formatting-related errors.

/**
 * Base class for formatting errors
 */
class FormatError extends Error {
  /** Error code identifying the type of error */
  readonly code: ErrorCode;
  /** Original message string being formatted (if available) */
  readonly originalMessage: string | undefined;
  
  /**
   * Create a new FormatError
   * @param msg - Error message
   * @param code - Error code
   * @param originalMessage - Original message string being formatted
   */
  constructor(msg: string, code: ErrorCode, originalMessage?: string);
  
  /**
   * Custom string representation
   * @returns Formatted error string with code
   */
  toString(): string;
}

Usage Examples:

import { FormatError, ErrorCode } from "intl-messageformat";

try {
  const msg = new IntlMessageFormat('Hello {name}!', 'en-US');
  msg.format({}); // Missing 'name' value
} catch (error) {
  if (error instanceof FormatError) {
    console.log(error.code); // ErrorCode.MISSING_VALUE
    console.log(error.originalMessage); // 'Hello {name}!'
    console.log(error.toString()); // "[formatjs Error: MISSING_VALUE] ..."
  }
}

MissingValueError Class

Thrown when a placeholder in the message has no corresponding value in the provided data.

/**
 * Error thrown when a required placeholder value is missing
 */
class MissingValueError extends FormatError {
  /**
   * Create a new MissingValueError
   * @param variableId - Name of the missing variable
   * @param originalMessage - Original message string being formatted
   */
  constructor(variableId: string, originalMessage?: string);
}

Usage Examples:

import IntlMessageFormat from "intl-messageformat";

try {
  const msg = new IntlMessageFormat('Welcome {firstName} {lastName}!', 'en-US');
  msg.format({ firstName: 'John' }); // Missing lastName
} catch (error) {
  console.log(error.message); 
  // "The intl string context variable "lastName" was not provided to the string "Welcome {firstName} {lastName}!""
}

InvalidValueError Class

Thrown when a value provided for a placeholder is not valid for that placeholder type (e.g., wrong option in a select statement).

/**
 * Error thrown when a provided value is invalid for the placeholder
 */
class InvalidValueError extends FormatError {
  /**
   * Create a new InvalidValueError
   * @param variableId - Name of the variable with invalid value
   * @param value - The invalid value that was provided
   * @param options - Valid options for this variable
   * @param originalMessage - Original message string being formatted
   */
  constructor(
    variableId: string,
    value: any,
    options: string[],
    originalMessage?: string
  );
}

Usage Examples:

import IntlMessageFormat from "intl-messageformat";

try {
  const msg = new IntlMessageFormat(
    '{status, select, active {Active} inactive {Inactive} other {Unknown}}',
    'en-US'
  );
  msg.format({ status: 'pending' }); // 'pending' not in options
} catch (error) {
  console.log(error.message);
  // 'Invalid values for "status": "pending". Options are "active", "inactive", "other"'
}

InvalidValueTypeError Class

Thrown when a value is of the wrong type for a placeholder (e.g., providing a string when a function is expected for XML tags).

/**
 * Error thrown when a value has the wrong type for the placeholder
 */
class InvalidValueTypeError extends FormatError {
  /**
   * Create a new InvalidValueTypeError
   * @param value - The variable name with wrong type
   * @param type - The expected type
   * @param originalMessage - Original message string being formatted
   */
  constructor(value: any, type: string, originalMessage?: string);
}

Usage Examples:

import IntlMessageFormat from "intl-messageformat";

try {
  const msg = new IntlMessageFormat(
    'Click <link>here</link> to continue.',
    'en-US'
  );
  msg.format({ link: 'not-a-function' }); // Should be a function
} catch (error) {
  console.log(error.message);
  // 'Value for "not-a-function" must be of type function'
}

Error Handling Patterns

Basic Error Handling

import IntlMessageFormat, { FormatError, ErrorCode } from "intl-messageformat";

function safeFormat(messageString: string, values: Record<string, any>, locale = 'en-US') {
  try {
    const msg = new IntlMessageFormat(messageString, locale);
    return msg.format(values);
  } catch (error) {
    if (error instanceof FormatError) {
      switch (error.code) {
        case ErrorCode.MISSING_VALUE:
          console.warn('Missing value:', error.message);
          return messageString; // Fallback to original
        case ErrorCode.INVALID_VALUE:
          console.warn('Invalid value:', error.message);
          return messageString;
        case ErrorCode.MISSING_INTL_API:
          console.error('Missing Intl API:', error.message);
          return messageString;
        default:
          console.error('Format error:', error.message);
          return messageString;
      }
    }
    throw error; // Re-throw non-format errors
  }
}

Validation Before Formatting

function validateMessageValues(
  messageString: string, 
  values: Record<string, any>,
  locale = 'en-US'
): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];
  
  try {
    const msg = new IntlMessageFormat(messageString, locale);
    msg.format(values);
    return { isValid: true, errors: [] };
  } catch (error) {
    if (error instanceof FormatError) {
      errors.push(error.message);
      return { isValid: false, errors };
    }
    throw error;
  }
}

// Usage
const validation = validateMessageValues(
  'Hello {name}, you have {count, plural, one {# message} other {# messages}}.',
  { name: 'Alice' } // Missing count
);
if (!validation.isValid) {
  console.log('Validation errors:', validation.errors);
}

Development vs Production Error Handling

const isDevelopment = process.env.NODE_ENV === 'development';

function formatMessage(messageString: string, values: Record<string, any>) {
  try {
    const msg = new IntlMessageFormat(messageString, 'en-US');
    return msg.format(values);
  } catch (error) {
    if (isDevelopment) {
      // In development, show detailed errors
      console.error('Message formatting failed:', {
        message: messageString,
        values,
        error: error.message
      });
      return `[FORMAT ERROR: ${error.message}]`;
    } else {
      // In production, fail silently with fallback
      console.warn('Message formatting failed silently');
      return messageString;
    }
  }
}

Error Prevention

Type-Safe Value Objects

// Define interfaces for your message values
interface UserMessage {
  name: string;
  count: number;
  status: 'active' | 'inactive';
}

function formatUserMessage(values: UserMessage) {
  const msg = new IntlMessageFormat(
    'Hello {name}! You have {count, number} items. Status: {status, select, active {Active} inactive {Inactive} other {Unknown}}',
    'en-US'
  );
  return msg.format(values);
}

// This will catch type errors at compile time
formatUserMessage({
  name: 'Alice',
  count: 5,
  status: 'active' // TypeScript ensures this is valid
});

Message Validation Utility

class MessageValidator {
  static validatePlaceholders(
    messageString: string,
    providedValues: Record<string, any>
  ): { missing: string[]; extra: string[] } {
    // Extract placeholders from message (simplified)
    const placeholders = messageString.match(/{([^}]+)}/g)?.map(
      match => match.slice(1, -1).split(',')[0].trim()
    ) || [];
    
    const provided = Object.keys(providedValues);
    const missing = placeholders.filter(p => !provided.includes(p));
    const extra = provided.filter(p => !placeholders.includes(p));
    
    return { missing, extra };
  }
}

// Usage
const validation = MessageValidator.validatePlaceholders(
  'Hello {name}!',
  { name: 'Alice', age: 25 }
);
console.log(validation); // { missing: [], extra: ['age'] }

docs

error-handling.md

formatters-types.md

index.md

message-formatting.md

tile.json