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.
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;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>;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"
}
}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!"
}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'
});Format compatible with Lokalise translation management system.
// Lokalise formatter (built-in: 'lokalise')Usage:
await extractAndWrite(['src/**/*.tsx'], {
format: 'lokalise',
outFile: 'lokalise/source.json'
});Format compatible with Smartling translation management system.
// Smartling formatter (built-in: 'smartling')Usage:
await extractAndWrite(['src/**/*.tsx'], {
format: 'smartling',
outFile: 'smartling/source.json'
});Format compatible with Transifex translation management system.
// Transifex formatter (built-in: 'transifex')Usage:
await extractAndWrite(['src/**/*.tsx'], {
format: 'transifex',
outFile: 'transifex/source.json'
});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);
};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'
});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'
})
]);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
});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);# 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# 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# Compile entire folder with specific formatter
formatjs compile-folder tms/lokalise/ compiled/ --format lokalise --ast| Formatter | Extraction | Compilation | Metadata | Use Case |
|---|---|---|---|---|
| default | Full descriptors | defaultMessage only | Complete | Development, source format |
| simple | Key-value only | Direct mapping | None | Simple workflows |
| crowdin | Crowdin compatible | Crowdin compatible | Limited | Crowdin TMS |
| lokalise | Lokalise compatible | Lokalise compatible | Limited | Lokalise TMS |
| smartling | Smartling compatible | Smartling compatible | Limited | Smartling TMS |
| transifex | Transifex compatible | Transifex compatible | Limited | Transifex TMS |
| custom | User defined | User defined | User defined | Custom workflows |
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);
}format and compile// 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'
});