Back Home

Post Detail

2026.09.11

6 min read

engineering / nextjs / i18n / open-source

How Astra Games Keeps Twelve Languages on One Content Update Path

Reading a multilingual game catalog from upstream Markdown: content ownership, isolated language caches, explicit fallback states, and connecting translated pages for the same work.

Astra Games architecture: upstream README files flow through independent language caches into twelve localized views

The simplest way to add languages to a catalog is to duplicate a page and translate it.

As new works arrive, the harder question becomes how many copies of the data each update must touch. Should a README change require another website edit? Should one unavailable translation break every other language?

For Astra Games, I started with content ownership. The awesome-gpt-6-astra collection maintains the work records. The website reads and presents them without maintaining a second manually edited list in its own code.

One authority can contain multiple files

The upstream repository has a README for each language. A locale registry maps the language code to its file, display label, HTML language value, and reading direction. English reads README.md; Simplified Chinese reads README.zh-CN.md.

Translations still need maintenance. A single source of truth means that work information has one owner, not that all localized content becomes one file.

Navigation, buttons, and status copy live in local dictionaries. Work titles, descriptions, and links come from the corresponding catalog. This lets the site's interface evolve without creating another editorial copy of the works. Locale registry and dictionaries

The parser also has a clear origin. It reuses the upstream website's implementation, adapted for Next.js bundling. Sharing those parsing rules helps prevent the collection and its website from drifting into different interpretations of the same Markdown. Catalog parser

Automatic updates need a freshness contract

Each language has an independent catalog service with its own cache, ETag, and last successful result. Successful reads are reused for five minutes. The next access after expiry triggers a refresh, while concurrent reads within the same process reuse the in-flight request. Catalog service

“Automatic synchronization” therefore means refresh on access with a cache window. It does not mean an upstream commit immediately pushes an update to every open page.

There is another subtle boundary between origin and CDN caching. If a result has already spent four minutes in the server cache, giving it a new five-minute CDN lifetime extends its freshness beyond the intended window. The implementation passes the remaining freshness budget to the CDN instead.

I find it useful to describe caching as a product contract: when readers can expect changes, what they see after a failure, and whether the page identifies that state accurately.

Fallback data has provenance and a lifetime

When upstream is temporarily unavailable, removing the whole catalog is not the only option:

text
Fresh cache → reuse it
Expired cache → request that language's README
    Success → update catalog and check time
    Failure → last successful result → bundled snapshot → unavailable

These outcomes should not all be presented as current data. The implementation distinguishes fresh, stale, fallback, and unavailable results, with a shorter retry interval after failure.

Bundled snapshots currently exist only for English and Simplified Chinese. Another language can become unavailable when a cold instance cannot reach upstream. It would be inaccurate to claim that all twelve languages have offline fallback.

The last successful result is also process-local state rather than durable storage shared across instances. After a restart or a new instance, available data depends on upstream access and bundled snapshots. Caching and fallback implementation

Making those limits explicit helps identify when shared caching becomes useful, rather than adding infrastructure before the catalog needs it.

Translation must preserve the identity of the work

A game's translated title can produce a different detail-page slug. Replacing the language segment of a URL therefore does not guarantee that the corresponding page exists.

The current implementation associates translations using the demo URL, then the source or repository URL when needed. It generates language alternates only for entries it actually finds. A missing translation or failed locale load can be omitted without failing the original detail page. Cross-language work matching

This depends on stable links. If a game later uses different demo domains by language, an explicit work ID would be a stronger identity. The existing approach is useful within the shape of today's collection, with that assumption kept visible.

Arabic supplies another reminder that localization includes more than strings. Its locale definition records rtl, which the layout passes to the page's dir attribute. Having a translated dictionary is not enough to establish the correct reading direction.

Supporting twelve languages ultimately tests the same maintenance decisions: where information changes, when cached data refreshes, how failures are represented, and how different pages continue to identify the same work.

Explore the source · Open the catalog

This article describes the implementation inspected on September 11, 2026.

    How Astra Games Keeps Twelve Languages on One Content Update Path