* Serve the locale catalogues as rspack chunks The catalogues were fetched from `app/locales/<locale>.json`, a path outside everything rspack does for an asset. They were unhashed, so `cacheable?` never granted them far-future headers, and uncompressed, so the server gzipped each one per request. Importing them instead puts them through the normal pipeline: rspack hashes each catalogue, emits it as its own chunk under `app/dist`, writes the `.br` and `.gz` siblings, and resolves the URL at runtime. A hashed file there is already cacheable, so no new rule is needed. Production swaps `devtool` for SourceMapDevToolPlugin, which takes an exclude. A source map for a catalogue maps JSON onto itself and would add about 29 MB. * Format the specs the locale change touched * Keep the catalogues out of the SDK bundle A context module bundles its whole directory wherever it resolves, so importing the catalogues added all 36 to the SDK's reachable graph: 66 files to 102, and its total gzipped size up 275%. The SDK should not carry them regardless of size. It renders in a customer's page against whichever instance it is pointed at, so its catalogue has to come from that instance rather than from whenever the SDK was built. `locale-catalog` now names how a catalogue is loaded, the way `ee-plugins` and `sdk-ee-plugins` name their implementations. The app imports from its own bundle; the SDK bundle config aliases the fetch instead, which also keeps the context module out of it. * Let the SDK cache the catalogues it fetches A catalogue served from `app/locales/<locale>.json` carries no hash, so `cacheable?` never matched it and the SDK re-downloaded one on every load. The SDK does not need the app's chunk hash to fix that, and it cannot have it: the two bundles are separate compilations with their own chunk ids, filename templates and runtime wrappers, so the same catalogue hashes differently in each. What it needs is any token that changes when the catalogues do, and it already has one. The instance serves the SDK bundle, so whatever build produced it produced the catalogues too, and `EnvironmentPlugin` already compiles that build's version and commit into it. The SDK now names its build in the query, and a catalogue request that carries one is cacheable. Without one it is not, so a caller that omits it cannot pin a stale catalogue. * Move the locale loading out of metabase/api Nothing here calls an API endpoint. The catalogue is a static asset, loaded by import in the app and by a plain fetch in the SDK, so it sits alongside `utils/i18n`, which applies what these return. * Swap the locale loader by path, not by alias The loader was reached through a `locale-catalog` alias, which had to be declared in the rspack aliases, tsconfig and jest config before the SDK bundle could override it: four places to say one thing. The importer now uses a plain relative import, which every resolver already understands, and the SDK config keys its override on the resolved path. One config knows a swap happens, and it is the one doing the swapping. * Let the SDK load catalogues as chunks too The SDK fetched catalogues from the instance over HTTP because a context import pulled all 36 into its bundle. They were never in its initial payload: an import() context always emits async chunks. What tripped CI was the gate itself, which is `embedding-sdk-chunked/total`, the reachable set. All 36 counted, 8.5 MB gzip against a 3.1 MB bundle, so 275%. The SDK can use the same loader. `sdk-public-path.ts` points `__webpack_public_path__` at the instance, and the package injects the bundle as a script tag, so a host app's bundler never sees the import(). Chunks land under `app/embedding-sdk/chunks/`, which the backend already serves with far-future immutable headers, so the build token and its `cacheable?` clause are not needed either. Measure the catalogues out of the reachable totals instead. One loads per session and none for English, so counting all 36 overstates by 35x, and their size tracks the translation process rather than any code change. Drops the loader, its spec, the build-time alias and the backend clause. The SDK's chunkFilename takes `[name]` so the gate can recognise them. * Fold the loader into its only caller Nothing overrides `loadLocaleCatalog` through the build any more, so it does not need to be its own module. Drop the SourceMapDevToolPlugin too. It existed to keep source maps off the locale chunks, which only cost disk in a dev build, and it made `devtool` conditional to work around the two not being able to run together. Clear the comments that explained the change rather than the code: the specs said what the catalogue used to do, and two references pointed at the file this commit removes. * Point the locale test hooks at the chunk The catalogue arrives as a hashed rspack chunk, so the specs that stubbed or waited on `GET /app/locales/<locale>.json` were watching a request that no longer happens. Five e2e specs used the intercept only as a synchronisation point, so they now wait on the chunk instead. `content-translations` stubbed an empty catalogue to keep the UI in English while it checks that content is translated, and a chunk cannot carry a JSON body, so it blocks the chunk. Its Arabic block asserts English chrome and `AR-` content only, nothing that needs the catalogue applied. That leans on LocaleProvider catching a failed load, so cover it: a rejected catalogue still renders the children. Also fix the import order the module move disturbed in three consumers. * Spy on loadLocalization instead of wrapping the module * Drop a tombstone comment * Keep the locale set when its catalogue fails to load Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUuaopJ7ZtMVvdwwxF6qyx * Wait on the language landing, not on the catalogue request Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUuaopJ7ZtMVvdwwxF6qyx * Keep the locales directory when the translations step clears it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUuaopJ7ZtMVvdwwxF6qyx * Assert a real Dutch string after the first language switch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUuaopJ7ZtMVvdwwxF6qyx * Re-render the children once the catalogue is applied Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUuaopJ7ZtMVvdwwxF6qyx --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Downloading Jars
mb-download will let you download a jar to a given path or straight into $JARS.
$ ./mb-download -h
Usage: mb-download 0.42.2
Usage: mb-download 1.45.2
Usage: mb-download 1.45.2 ~/path/to/my/jars
protip: this script will read from $JARS, and use that as your jar directory.