Bazel rules for FormatJS - Internationalize your web apps on the client & server.
- Extract messages from source files (TypeScript, JavaScript, JSX, TSX)
- 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
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>",
)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 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 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 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_messagesload("@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"],
)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
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)
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
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>
);
}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
The formatjs_aggregate_aspect collects and merges extracted messages from a target and all its dependencies into a single JSON file using jq.
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_messagesOutput: 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.
The library also includes demonstration aspects in formatjs/aspects.bzl:
bazel build //path/to:target \
--aspects=@rules_formatjs//formatjs:aspects.bzl%message_stats_aspect \
--output_groups=message_statsbazel build //path/to:target \
--aspects=@rules_formatjs//formatjs:aspects.bzl%message_validator_aspect \
--output_groups=message_validationbazel build //path/to:target \
--aspects=@rules_formatjs//formatjs:aspects.bzl%message_collector_aspect \
--output_groups=all_messagesload("@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",
)rules_formatjs uses a native Rust CLI toolchain that is automatically downloaded for your platform. The toolchain supports:
- macOS (Apple Silicon and Intel)
- Linux (x86_64)
The toolchain is registered automatically when you add the MODULE.bazel dependency. Binaries are fetched from GitHub releases and cached by Bazel.
See the examples directory for complete working examples:
- examples/simple - Basic message extraction, compilation, and verification
- examples/aggregate - Aggregating messages from multiple modules
See CONTRIBUTING.md for development instructions.
Apache License 2.0