A comprehensive CLI for internationalization workflows with message extraction, compilation, and verification capabilities.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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'
});