or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

compilation.mdextraction.mdformatters.mdindex.mdverification.md
tile.json

formatters.mddocs/

Format System

Pluggable formatter system for converting between different translation management system (TMS) formats, with built-in support for major TMS providers and the ability to create custom formatters.

Capabilities

Formatter Interface

Core interface that all formatters must implement for processing translation data.

/**
 * Formatter interface for processing translation data
 */
interface Formatter<T> {
  /** Optional serialization function for custom output formatting */
  serialize?: SerializeFn<T>;
  /** Format function for converting extracted messages to TMS format */
  format: FormatFn<T>;
  /** Compile function for converting TMS format back to react-intl format */
  compile: CompileFn<T>;
  /** Optional message comparison function for sorting/deduplication */
  compareMessages?: Comparator;
}

/** Format function type for converting messages to TMS format */
type FormatFn<T = Record<string, MessageDescriptor>> = (
  msgs: Record<string, MessageDescriptor>
) => T;

/** Compile function type for converting TMS format to react-intl format */
type CompileFn<T = Record<string, MessageDescriptor>> = (
  msgs: T
) => Record<string, string>;

/** Serialization function type for custom string output */
type SerializeFn<T = Record<string, MessageDescriptor>> = (
  msgs: T
) => string;

/** Message comparison function for sorting */
type Comparator = (a: {key: string, value: any}, b: {key: string, value: any}) => number;

/** Generic element type for JSON comparison */
type Element = any;

Formatter Resolution

Resolves built-in formatters by name or loads custom formatter modules.

/**
 * Resolve built-in formatter by name or load custom formatter
 * @param format - Formatter name or path to custom formatter, or Formatter object
 * @returns Promise resolving to formatter module
 */
function resolveBuiltinFormatter(
  format?: string | Formatter<unknown>
): Promise<any>;

Built-in Formatters

Default Formatter

Standard FormatJS format that preserves complete message descriptor structure.

// Default format functions
const format: FormatFn = msgs => msgs;

const compile: CompileFn = msgs => {
  const results: Record<string, string> = {};
  for (const k in msgs) {
    results[k] = msgs[k].defaultMessage!;
  }
  return results;
};

Usage:

import { extractAndWrite } from "@formatjs/cli-lib";

// Uses default formatter (no format specified)
await extractAndWrite(['src/**/*.tsx'], {
  outFile: 'messages/extracted.json'
});

Output Format:

{
  "greeting": {
    "id": "greeting",
    "defaultMessage": "Hello {name}!",
    "description": "Greeting message for users"
  }
}

Simple Formatter

Simplified key-value format without metadata.

// Simple formatter (built-in: 'simple')

Usage:

await extractAndWrite(['src/**/*.tsx'], {
  format: 'simple',
  outFile: 'messages/simple.json'
});

Output Format:

{
  "greeting": "Hello {name}!",
  "farewell": "Goodbye!"
}

Crowdin Formatter

Format compatible with Crowdin translation management system.

// Crowdin formatter (built-in: 'crowdin')

Usage:

await extractAndWrite(['src/**/*.tsx'], {
  format: 'crowdin',
  outFile: 'crowdin/source.json'
});

await compileAndWrite(['crowdin/translated-fr.json'], {
  format: 'crowdin',
  outFile: 'compiled/fr.json'
});

Lokalise Formatter

Format compatible with Lokalise translation management system.

// Lokalise formatter (built-in: 'lokalise')

Usage:

await extractAndWrite(['src/**/*.tsx'], {
  format: 'lokalise',
  outFile: 'lokalise/source.json'
});

Smartling Formatter

Format compatible with Smartling translation management system.

// Smartling formatter (built-in: 'smartling')

Usage:

await extractAndWrite(['src/**/*.tsx'], {
  format: 'smartling',
  outFile: 'smartling/source.json'
});

Transifex Formatter

Format compatible with Transifex translation management system.

// Transifex formatter (built-in: 'transifex')

Usage:

await extractAndWrite(['src/**/*.tsx'], {
  format: 'transifex',
  outFile: 'transifex/source.json'
});

Custom Formatters

Creating Custom Formatters

Create a custom formatter by implementing the Formatter interface:

// custom-formatter.js
export const format = (msgs) => {
  // Transform extracted messages to your custom format
  const customFormat = {};
  for (const [key, descriptor] of Object.entries(msgs)) {
    customFormat[key] = {
      text: descriptor.defaultMessage,
      context: descriptor.description,
      metadata: {
        id: descriptor.id,
        file: descriptor.file
      }
    };
  }
  return customFormat;
};

export const compile = (msgs) => {
  // Transform your custom format back to react-intl format
  const compiled = {};
  for (const [key, value] of Object.entries(msgs)) {
    compiled[key] = value.text;
  }
  return compiled;
};

export const serialize = (msgs) => {
  // Optional: custom string serialization
  return JSON.stringify(msgs, null, 2);
};

export const compareMessages = (a, b) => {
  // Optional: custom message comparison for sorting
  return a.key.localeCompare(b.key);
};

Using Custom Formatters

import { extractAndWrite, compileAndWrite } from "@formatjs/cli-lib";

// Use custom formatter for extraction
await extractAndWrite(['src/**/*.tsx'], {
  format: './formatters/my-formatter.js',
  outFile: 'custom/extracted.json'
});

// Use custom formatter for compilation
await compileAndWrite(['custom/translated-fr.json'], {
  format: './formatters/my-formatter.js',
  outFile: 'compiled/fr.json'
});

Usage Examples

Extraction with Different Formatters

import { extractAndWrite } from "@formatjs/cli-lib";

// Extract to different TMS formats
await Promise.all([
  // Crowdin format
  extractAndWrite(['src/**/*.tsx'], {
    format: 'crowdin',
    outFile: 'tms/crowdin/source.json'
  }),
  
  // Lokalise format
  extractAndWrite(['src/**/*.tsx'], {
    format: 'lokalise', 
    outFile: 'tms/lokalise/source.json'
  }),
  
  // Simple key-value format
  extractAndWrite(['src/**/*.tsx'], {
    format: 'simple',
    outFile: 'simple/messages.json'
  })
]);

Compilation with Formatters

import { compileAndWrite } from "@formatjs/cli-lib";

// Compile from different TMS formats
await compileAndWrite(['tms/crowdin/fr.json'], {
  format: 'crowdin',
  outFile: 'compiled/fr.json',
  ast: true
});

await compileAndWrite(['tms/smartling/es.json'], {
  format: 'smartling', 
  outFile: 'compiled/es.json',
  ast: true
});

Programmatic Formatter Usage

import { resolveBuiltinFormatter, extract } from "@formatjs/cli-lib";

// Get formatter and use programmatically
const crowdinFormatter = await resolveBuiltinFormatter('crowdin');

const result = await extract(['src/components/*.tsx'], {
  format: crowdinFormatter
});

// Apply custom processing
const customProcessed = crowdinFormatter.format(result.messages);

CLI Usage

Extraction with Formatters

# Extract to Crowdin format
formatjs extract 'src/**/*.tsx' --format crowdin --out-file crowdin/source.json

# Extract to Lokalise format
formatjs extract 'src/**/*.tsx' --format lokalise --out-file lokalise/source.json

# Extract with custom formatter
formatjs extract 'src/**/*.tsx' --format ./formatters/custom.js --out-file custom/source.json

# Extract to simple key-value format
formatjs extract 'src/**/*.tsx' --format simple --out-file simple/messages.json

Compilation with Formatters

# Compile from Crowdin format
formatjs compile crowdin/fr.json --format crowdin --out-file compiled/fr.json --ast

# Compile from Smartling format
formatjs compile smartling/es.json --format smartling --out-file compiled/es.json

# Compile with custom formatter
formatjs compile custom/de.json --format ./formatters/custom.js --out-file compiled/de.json

Batch Operations with Formatters

# Compile entire folder with specific formatter
formatjs compile-folder tms/lokalise/ compiled/ --format lokalise --ast

Formatter Comparison

FormatterExtractionCompilationMetadataUse Case
defaultFull descriptorsdefaultMessage onlyCompleteDevelopment, source format
simpleKey-value onlyDirect mappingNoneSimple workflows
crowdinCrowdin compatibleCrowdin compatibleLimitedCrowdin TMS
lokaliseLokalise compatibleLokalise compatibleLimitedLokalise TMS
smartlingSmartling compatibleSmartling compatibleLimitedSmartling TMS
transifexTransifex compatibleTransifex compatibleLimitedTransifex TMS
customUser definedUser definedUser definedCustom workflows

Error Handling

import { resolveBuiltinFormatter, extractAndWrite } from "@formatjs/cli-lib";

try {
  // This will throw if formatter doesn't exist
  const formatter = await resolveBuiltinFormatter('./nonexistent-formatter.js');
} catch (error) {
  console.error('Cannot resolve formatter:', error.message);
}

try {
  await extractAndWrite(['src/**/*.tsx'], {
    format: 'invalid-formatter',
    outFile: 'output.json'
  });
} catch (error) {
  console.error('Formatter error:', error.message);
}

Best Practices

Formatter Selection

  • Use default for development and when you need complete message metadata
  • Use simple for basic key-value workflows without metadata
  • Use TMS-specific formatters (crowdin, lokalise, etc.) when integrating with those services
  • Create custom formatters when you have specific format requirements

Custom Formatter Guidelines

  1. Implement all required functions: At minimum, implement format and compile
  2. Handle edge cases: Account for missing fields, malformed data
  3. Preserve message integrity: Ensure round-trip conversion preserves message content
  4. Document format: Clearly document your custom format structure
  5. Test thoroughly: Test extraction and compilation with real translation data

Integration Patterns

// Environment-specific formatter selection
const getFormatter = (env) => {
  switch (env) {
    case 'crowdin': return 'crowdin';
    case 'lokalise': return 'lokalise';
    case 'development': return 'default';
    default: return './formatters/production.js';
  }
};

await extractAndWrite(['src/**/*.tsx'], {
  format: getFormatter(process.env.TMS_PROVIDER),
  outFile: 'extracted.json'
});