Skip to content

Commit db81e16

Browse files
committed
fix: more docs
1 parent c3e5cc8 commit db81e16

7 files changed

Lines changed: 728 additions & 107 deletions

File tree

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ package_metadata(
1313

1414
license(
1515
name = "license",
16-
kind = "@package_metadata//licenses/spdx:Apache-2.0",
16+
kind = "@package_metadata//licenses/spdx:MIT",
1717
text = "LICENSE",
1818
)

MODULE.bazel.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

formatjs/aggregate.bzl

Lines changed: 215 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
1-
"""Aspect for aggregating extracted messages across dependency trees
2-
3-
This aspect collects all extracted messages from a target and its dependencies,
4-
merging them into a single JSON file.
1+
"""Rules and aspects for aggregating messages across dependency trees.
2+
3+
This module provides tools for merging extracted messages from multiple targets
4+
and their dependencies into a single, consolidated JSON file. This is useful for
5+
large monorepo projects where messages are extracted from multiple packages or
6+
modules and need to be combined for translation or compilation.
7+
8+
The aggregation process:
9+
1. Traverses the dependency graph collecting all extracted message files
10+
2. Merges messages using jq with object multiplication semantics
11+
3. Sorts keys alphabetically for deterministic output
12+
4. Handles duplicate message IDs (later values override earlier ones)
13+
14+
Aggregation can be used via:
15+
- `formatjs_aggregate` rule: Declarative approach for common use cases
16+
- `formatjs_aggregate_aspect`: Aspect-based approach for advanced scenarios
517
"""
618

719
load(":extract.bzl", "FormatjsExtractInfo")
820

921
FormatjsAggregateInfo = provider(
10-
doc = "Aggregated messages from a target and its dependencies",
22+
doc = """Provider containing aggregated messages from multiple targets.
23+
24+
This provider is returned by both the `formatjs_aggregate` rule and the
25+
`formatjs_aggregate_aspect`. It contains the collected message files and
26+
metadata about the aggregation.
27+
""",
1128
fields = {
12-
"messages": "depset of message JSON files",
13-
"count": "Number of message files collected",
29+
"messages": "depset of message JSON files collected from the target and its dependencies",
30+
"count": "Number of message files collected (integer)",
1431
},
1532
)
1633

@@ -82,22 +99,40 @@ formatjs_aggregate_aspect = aspect(
8299
implementation = _formatjs_aggregate_aspect_impl,
83100
attr_aspects = ["deps", "srcs"],
84101
toolchains = ["@jq.bzl//jq/toolchain:type"],
85-
doc = """Aspect that aggregates extracted messages across dependencies.
102+
doc = """Aspect that aggregates extracted messages across dependency graphs.
86103
87-
This aspect collects all FormatJS extracted message files from a target and
88-
its transitive dependencies, merging them into a single JSON file using jq.
104+
This aspect provides a powerful way to collect and merge FormatJS messages from
105+
a target and all its transitive dependencies. It automatically traverses the
106+
dependency graph, collecting messages from any target that provides `FormatjsExtractInfo`.
89107
90-
The merge strategy uses jq's object multiplication (*) which merges objects
91-
with later values overwriting earlier ones for duplicate keys.
108+
## How It Works
92109
93-
Usage:
94-
# Aggregate messages from a target and all its dependencies
95-
bazel build //path/to:target \\
96-
--aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \\
97-
--output_groups=aggregated_messages
110+
The aspect:
111+
1. Attaches to targets via `deps` and `srcs` attributes
112+
2. Collects message files from targets with `FormatjsExtractInfo`
113+
3. Recursively collects from dependencies
114+
4. Merges all messages into a single JSON file using jq
115+
5. Sorts keys alphabetically for deterministic builds
116+
117+
## Merge Strategy
98118
99-
# Get all individual message files (not merged)
100-
bazel build //path/to:target \\
119+
Messages are merged using jq's object multiplication (`*`) operator:
120+
- Later values override earlier ones for duplicate message IDs
121+
- All unique message IDs are preserved
122+
- Sorted alphabetically by key in the final output
123+
124+
## Usage Patterns
125+
126+
### Basic aspect usage - aggregate all messages:
127+
```bash
128+
bazel build //app:main \\
129+
--aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \\
130+
--output_groups=aggregated_messages
131+
```
132+
133+
### Get individual message files (not merged):
134+
```bash
135+
bazel build //app:main \\
101136
--aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \\
102137
--output_groups=all_messages
103138
@@ -168,31 +203,173 @@ formatjs_aggregate = rule(
168203
implementation = _formatjs_aggregate_impl,
169204
attrs = {
170205
"deps": attr.label_list(
171-
doc = "Dependencies to aggregate messages from. Should be formatjs_extract targets.",
206+
doc = """Dependencies to aggregate messages from.
207+
208+
Should be `formatjs_extract` targets or other targets that provide
209+
`FormatjsExtractInfo`. The rule will automatically apply the
210+
`formatjs_aggregate_aspect` to traverse the entire dependency graph
211+
and collect all messages.
212+
213+
Example:
214+
```starlark
215+
deps = [
216+
"//frontend:messages",
217+
"//backend:messages",
218+
"//shared:messages",
219+
]
220+
```
221+
""",
172222
aspects = [formatjs_aggregate_aspect],
173223
),
174224
},
175225
toolchains = ["@jq.bzl//jq/toolchain:type"],
176-
doc = """Rule that aggregates messages from dependencies.
226+
doc = """Aggregate messages from multiple extraction targets into a single file.
177227
178-
This rule automatically applies the formatjs_aggregate_aspect to traverse
179-
dependencies and collect all messages, then merges them into a single JSON file.
228+
This rule provides a declarative way to merge messages from multiple `formatjs_extract`
229+
targets across your codebase. It's ideal for monorepos where messages are extracted
230+
from different packages or modules and need to be combined for translation workflows.
180231
181-
Example:
182-
```starlark
183-
formatjs_aggregate(
184-
name = "all_messages",
185-
deps = [
186-
"//module1:messages",
187-
"//module2:messages",
188-
"//module3:messages",
189-
],
190-
)
232+
## Features
233+
234+
- **Automatic Traversal**: Automatically applies aspect to collect messages from dependencies
235+
- **Transitive Collection**: Gathers messages from the entire dependency graph
236+
- **Merge & Sort**: Merges all messages and sorts keys alphabetically
237+
- **Duplicate Handling**: Later values override earlier ones for duplicate message IDs
238+
- **Simple API**: Just list your extraction targets as deps
239+
240+
## How It Works
241+
242+
1. The rule attaches `formatjs_aggregate_aspect` to all `deps`
243+
2. The aspect traverses the dependency graph collecting message files
244+
3. All collected messages are merged using jq
245+
4. Keys are sorted alphabetically for deterministic output
246+
5. Result is written to `<name>.json`
247+
248+
## Use Cases
249+
250+
### Monorepo with multiple packages:
251+
```starlark
252+
# Package 1 - Frontend
253+
formatjs_extract(
254+
name = "frontend_messages",
255+
srcs = glob(["frontend/**/*.tsx"]),
256+
)
257+
258+
# Package 2 - Backend
259+
formatjs_extract(
260+
name = "backend_messages",
261+
srcs = glob(["backend/**/*.tsx"]),
262+
)
263+
264+
# Aggregate all messages
265+
formatjs_aggregate(
266+
name = "all_messages",
267+
deps = [
268+
":frontend_messages",
269+
":backend_messages",
270+
],
271+
)
272+
```
273+
274+
### Multi-module application:
275+
```starlark
276+
formatjs_aggregate(
277+
name = "app_messages",
278+
deps = [
279+
"//modules/auth:messages",
280+
"//modules/dashboard:messages",
281+
"//modules/settings:messages",
282+
"//modules/admin:messages",
283+
],
284+
)
285+
```
286+
287+
### Nested aggregation:
288+
```starlark
289+
# Aggregate messages per feature
290+
formatjs_aggregate(
291+
name = "feature_a_messages",
292+
deps = [
293+
"//features/a/component1:messages",
294+
"//features/a/component2:messages",
295+
],
296+
)
297+
298+
# Aggregate all features
299+
formatjs_aggregate(
300+
name = "all_features",
301+
deps = [
302+
":feature_a_messages",
303+
"//features/b:messages",
304+
"//features/c:messages",
305+
],
306+
)
307+
```
308+
309+
## Output
310+
311+
The rule produces a JSON file named `<target_name>.json` in bazel-bin:
312+
```bash
313+
bazel build //:all_messages
314+
# Output: bazel-bin/all_messages.json
315+
```
316+
317+
The output format is standard FormatJS JSON:
318+
```json
319+
{
320+
"app.auth.login": {
321+
"id": "app.auth.login",
322+
"defaultMessage": "Log in",
323+
"description": "Login button"
324+
},
325+
"app.dashboard.welcome": {
326+
"id": "app.dashboard.welcome",
327+
"defaultMessage": "Welcome back!",
328+
"description": "Dashboard greeting"
329+
}
330+
}
331+
```
332+
333+
## Duplicate Message IDs
334+
335+
If the same message ID appears in multiple sources, the last one wins:
336+
- Messages are merged in the order they appear in the dependency graph
337+
- Later definitions override earlier ones
338+
- This allows for intentional overrides (e.g., customizing messages per module)
339+
340+
## Usage in Translation Workflow
341+
342+
```starlark
343+
# 1. Aggregate all messages
344+
formatjs_aggregate(
345+
name = "source_messages",
346+
deps = ["//..."], # All extraction targets
347+
)
348+
349+
# 2. Verify translations
350+
formatjs_verify_test(
351+
name = "verify_translations",
352+
translations = [
353+
":source_messages",
354+
"translations/fr.json",
355+
"translations/de.json",
356+
],
357+
)
358+
359+
# 3. Compile for production
360+
formatjs_compile(
361+
name = "compiled_fr",
362+
src = "translations/fr.json",
363+
out = "compiled-fr.json",
364+
ast = True,
365+
)
366+
```
367+
368+
## See Also
191369
192-
# Simply build the target to get the aggregated messages:
193-
# bazel build //:all_messages
194-
#
195-
# The output will be at: bazel-bin/all_messages.json
196-
```
370+
- `formatjs_extract`: Extract messages from source files
371+
- `formatjs_aggregate_aspect`: Lower-level aspect for advanced use cases
372+
- `formatjs_verify_test`: Verify translations against aggregated messages
373+
- `formatjs_compile`: Compile aggregated messages for production
197374
""",
198375
)

0 commit comments

Comments
 (0)