Bazel rules for FormatJS - Internationalize your web apps on the client & server.
Features
- Extract messages from source files (TypeScript, JavaScript, JSX, TSX, Rust)
- Compile messages for optimized runtime performance
- Verify translations for completeness
- Aggregate messages across multiple modules
- Native Rust CLI toolchain for fast builds
- Type-safe message extraction and compilation
Installation
Add rules_formatjs to your MODULE.bazel:
bazel_dep(name = "rules_formatjs", version = "0.1.0")
Or use a specific commit from GitHub:
git_override( module_name = "rules_formatjs", remote = "https://github.com/formatjs/rules_formatjs.git", commit = "<commit-sha>", )
Usage
Run the FormatJS CLI
The platform-specific CLI selected by the registered toolchain is directly runnable:
bazel run @rules_formatjs//cli -- --version
The same label can be used in executable or tool attributes. Apply the execution transition when declaring a Starlark attribute:
"_formatjs_cli": attr.label( default = Label("@rules_formatjs//cli"), executable = True, cfg = "exec", )
Extract Messages
Extract internationalized messages from your source code:
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_extract") formatjs_extract( name = "messages_extracted", srcs = glob(["src/**/*.tsx", "src/**/*.ts"]), out = "en.json", id_interpolation_pattern = "[sha512:contenthash:base64:6]", )
Compile Messages
Compile extracted messages for optimized runtime:
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_compile") formatjs_compile( name = "messages_compiled", src = ":messages_extracted", out = "en-compiled.json", ast = True, # Compile to AST for better performance )
Verify Translations
Verify that translations are complete and correctly formatted:
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_verify") formatjs_verify( name = "verify_translations", reference = ":messages_extracted", # Base language translations = [ "translations/es.json", "translations/fr.json", ], )
Aggregate Messages
Aggregate messages from multiple modules into a single file:
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_extract", "formatjs_aggregate") # Extract from multiple modules formatjs_extract(name = "module1_msgs", srcs = ["module1/**/*.tsx"]) formatjs_extract(name = "module2_msgs", srcs = ["module2/**/*.tsx"]) formatjs_extract(name = "module3_msgs", srcs = ["module3/**/*.tsx"]) # Create aggregation target formatjs_aggregate( name = "all_messages", deps = [":module1_msgs", ":module2_msgs", ":module3_msgs"], )
Then build with the aspect:
bazel build //:all_messages \ --aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \ --output_groups=aggregated_messages
Complete Example
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_extract", "formatjs_compile", "formatjs_verify") # Extract messages from source files formatjs_extract( name = "extract_en", srcs = glob([ "src/**/*.ts", "src/**/*.tsx", ]), out = "lang/en.json", id_interpolation_pattern = "[sha512:contenthash:base64:6]", extract_from_format_message_call = True, ) # Compile for production formatjs_compile( name = "compile_en", src = ":extract_en", out = "lang/en-compiled.json", ast = True, ) # Compile translations formatjs_compile( name = "compile_es", src = "translations/es.json", out = "lang/es-compiled.json", ast = True, ) # Verify translations are complete formatjs_verify( name = "verify_es", reference = ":extract_en", translations = ["translations/es.json"], )
API Reference
formatjs_extract
Extract messages from source files. This is a custom Bazel rule (not a macro), which means you can attach aspects to it for advanced build graph analysis.
Attributes:
name(required): Target namesrcs(required): List of source files to extract fromout(optional): Output JSON file (defaults toname + ".json")id_interpolation_pattern(optional): Pattern for message ID generation- Example:
"[sha512:contenthash:base64:6]"for content-based IDs
- Example:
extract_from_format_message_call(optional): Extract fromformatMessage()callsadditional_component_names(optional): Additional component names to extract fromadditional_function_names(optional): Additional function names to extract fromignore(optional): List of glob patterns to ignore
Providers:
DefaultInfo: Contains the extracted messages JSON fileFormatjsExtractInfo: Custom provider with:messages: File containing extracted messagessrcs: Depset of source filesid_interpolation_pattern: Pattern used for ID generation
formatjs_compile
Compile extracted messages for optimized runtime.
Attributes:
name(required): Target namesrc(required): Source JSON file with extracted messagesout(optional): Output compiled JSON file (defaults toname + ".json")ast(optional): Compile to AST format for better runtime performanceformat(optional): Input format (simple,crowdin,smartling,transifex)
formatjs_verify
Verify that translations are complete and correctly formatted.
Attributes:
name(required): Target namereference(required): Reference messages file (typically the base language)translations(required): List of translation files to verify
Integration with React
Use compiled messages with react-intl:
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl'; import messages from './lang/en-compiled.json'; const cache = createIntlCache(); const intl = createIntl( { locale: 'en', messages, }, cache ); function App() { return ( <RawIntlProvider value={intl}> {/* Your app */} </RawIntlProvider> ); }
Advanced: Using Aspects
Since formatjs_extract is a custom rule, you can attach Bazel aspects to it for advanced analysis and transformations. This is useful for:
- Collecting statistics about extracted messages
- Validating message format and completeness
- Aggregating messages across multiple targets
- Custom reporting and analysis
Message Aggregation Aspect
The formatjs_aggregate_aspect collects and merges extracted messages from a target and all its dependencies into a single JSON file using jq.
Example
load("@rules_formatjs//formatjs:defs.bzl", "formatjs_extract", "formatjs_aggregate") # Extract from multiple modules formatjs_extract(name = "module1_msgs", srcs = ["module1/**/*.tsx"]) formatjs_extract(name = "module2_msgs", srcs = ["module2/**/*.tsx"]) formatjs_extract(name = "module3_msgs", srcs = ["module3/**/*.tsx"]) # Create aggregation target formatjs_aggregate( name = "all_messages", deps = [":module1_msgs", ":module2_msgs", ":module3_msgs"], )
Then build with the aspect:
bazel build //:all_messages \ --aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \ --output_groups=aggregated_messages
Output: bazel-bin/all_messages_aggregated_messages.json containing all merged messages.
Merge Strategy: Uses jq to merge JSON objects. Later files overwrite earlier ones for duplicate keys.
Example Aspects
The library also includes demonstration aspects in formatjs/aspects.bzl:
Message Statistics
bazel build //path/to:target \ --aspects=@rules_formatjs//formatjs:aspects.bzl%message_stats_aspect \ --output_groups=message_stats
Message Validation
bazel build //path/to:target \ --aspects=@rules_formatjs//formatjs:aspects.bzl%message_validator_aspect \ --output_groups=message_validation
Message Collection (across dependencies)
bazel build //path/to:target \ --aspects=@rules_formatjs//formatjs:aspects.bzl%message_collector_aspect \ --output_groups=all_messages
Creating Custom Aspects
load("@rules_formatjs//formatjs:defs.bzl", "FormatjsExtractInfo") def _my_aspect_impl(target, ctx): if FormatjsExtractInfo not in target: return [] info = target[FormatjsExtractInfo] # Access extracted message file messages_file = info.messages # Access source files src_files = info.srcs.to_list() # Perform custom analysis... return [OutputGroupInfo(...)] my_aspect = aspect( implementation = _my_aspect_impl, doc = "My custom aspect for formatjs_extract", )
Toolchain
rules_formatjs uses a native Rust CLI toolchain that is automatically downloaded for your platform. The toolchain supports:
- macOS (Apple Silicon)
- Linux (x86_64 and ARM64)
- Windows (x86_64)
The toolchain is registered automatically when you add the MODULE.bazel dependency. Binaries are fetched from GitHub releases and cached by Bazel.
Examples
See the examples directory for complete working examples:
- examples/simple - Basic message extraction, compilation, and verification
- examples/aggregate - Aggregating messages from multiple modules
Resources
Contributing
See CONTRIBUTING.md for development instructions.
License
Apache License 2.0