Skip to content

CatFrame Localization System

dfdvdsf edited this page Jun 23, 2026 · 1 revision

JSON Localization System

CatFrame ships a namespace-based JSON localization system — similar to how higher Minecraft versions handle translations. Instead of .lang files, you write JSON key-value maps under assets/{modid}/lang/, and the system auto-detects the current Minecraft language at runtime.

How It Works

  1. During preInit: each mod registers its language domain with a modid and a classpath-relative path to its JSON lang directory.
  2. LocalizationManager.load() scans all registered domains, loads en_us.json as fallback, then loads the current language's JSON. Missing keys fall back to en_us automatically.
  3. At runtime: you request translations via Text wrappers or by calling LocalizationManager.Translation.translate() directly.

JSON files are plain key-value maps — no domain: prefix needed in the keys. The system prepends the domain automatically when loading.

File naming is always lowercase: en_us.json, zh_cn.json, ja_jp.json, etc. — matching high-version Minecraft convention.

assets/catframe/lang/en_us.json:

{
  "menu.paused": "Paused",
  "item.example.count": "%d / %d items",
  "ui.layouts.default": "Default"
}

assets/catframe/lang/zh_cn.json:

{
  "menu.paused": "暂停",
  "item.example.count": "%d / %d 个物品",
  "ui.layouts.default": "默认"
}

If the player's language is zh_cn, keys resolve to Chinese. Otherwise they fall back to en_us. If a key is missing in both, the raw domain:key string is returned instead.

LanguageRegister

Package: decok.dfcdvadstf.catframe.langguage.LanguageRegister

Register your mod's language domain before calling LocalizationManager.load():

import decok.dfcdvadstf.catframe.langguage.LanguageRegister;
import decok.dfcdvadstf.catframe.langguage.LocalizationManager;

// In preInit, client side:
LanguageRegister.domain(Tags.MODID, "assets/catframe/lang");
LanguageRegister.domain("mymod", "assets/mymod/lang");
LocalizationManager.Loader.load();
Method Description
domain(String modid, String basePath) Register a language domain. basePath is the classpath-relative directory containing en_us.json, zh_cn.json, etc.
getDomains() Returns an unmodifiable Map<String, String> of all registered domains.

LocalizationManager

Package: decok.dfcdvadstf.catframe.langguage.LocalizationManager

The singleton that resolves translation keys. Keys are always scoped by domain — internally stored as domain:key.

Translation Lookup

Two forms, just like ResourceLocation:

// Colon format
String text = LocalizationManager.Translation.translate("catframe:menu.paused");
String text2 = LocalizationManager.Translation.translate("catframe:item.count", 5, 10);

// Explicit domain + key
String text3 = LocalizationManager.Translation.translate("catframe", "menu.paused");
String text4 = LocalizationManager.Translation.translate("catframe", "item.count", 5, 10);

Format arguments use String.format semantics (%d, %s, etc.). If formatting fails or the key is missing, the raw key is returned with a log warning.

Block/Item Description IDs

LocalizationManager.DescriptionIds.make() generates translation keys in high-version Minecraft's type.namespace.name format — the same pattern used by vanilla Block.getName() and Item.getName():

// generate description IDs
String blockKey = LocalizationManager.DescriptionIds.make("block", "minecraft", "stone");
// → "block.minecraft.stone"

String itemKey  = LocalizationManager.DescriptionIds.make("item", "catframe", "meat_raw");
// → "item.catframe.meat_raw"

Then put the corresponding entry in your lang JSON:

{
  "stone": "Stone",
  "meat_raw": "Raw Meat"
}

The stone key is loaded under domain minecraft, and meat_raw under catframe — no prefix needed in the JSON.

translateToLocal (StatCollector-style)

For a familiar 1.7.10 API, use translateToLocal() — it accepts both domain:key and description IDs:

// domain:key — pulled apart at the colon
String s1 = LocalizationManager.Translation.translateToLocal("catframe:menu.paused");

// description ID — parsed as type.namespace.key
String s2 = LocalizationManager.Translation.translateToLocal("block.minecraft.stone");
String s3 = LocalizationManager.Translation.translateToLocal("item.catframe.meat_raw");

// explicit domain + key (no guessing required)
String s4 = LocalizationManager.Translation.translateToLocal("catframe", "menu.paused");

translateToLocal("block.minecraft.stone") walks the dots: first dot is the type prefix (block), second dot marks the namespace boundary (minecraft), everything after is the key (stone). So it resolves to translate("minecraft", "stone").

Enabled-Key Tracking

Every time a Text is created with setTranslatable() or Text.translatable(), the key is automatically marked as "enabled". You can query this: check which keys your UI actually uses, verify coverage, or log untranslated keys at dev time:

LocalizationManager.KeyTracking.mark("mymod", "some.key");  // (called automatically by Text)
boolean used = LocalizationManager.KeyTracking.isEnabled("mymod", "some.key");
Set<String> allUsed = LocalizationManager.KeyTracking.all();
Method Description
Loader.load() Load translations from all registered domains (client-side only).
Loader.reload() Reload after runtime language switch.
Translation.translate(String resourceKey, Object... args) Resolve domain:key → translated string.
Translation.translate(String domain, String key, Object... args) Resolve explicit domain + key.
Translation.translateToLocal(String key, Object... args) StatCollector-style: accepts domain:key or type.namespace.name description IDs.
Translation.translateToLocal(String domain, String key, Object... args) StatCollector-style with explicit domain + key.
DescriptionIds.make(String type, String domain, String name) Generate a description ID like "block.minecraft.stone".
Translation.hasKey(String resourceKey) Check if a domain:key exists.
Translation.hasKey(String domain, String key) Check if a domain+key pair exists.
KeyTracking.mark(String domain, String key) Mark a key as used (called automatically by Text).
KeyTracking.isEnabled(String domain, String key) Check if a key has been marked.
KeyTracking.all() Get all enabled domain:key strings.

Tip: Call KeyTracking.all() at the end of dev and diff it against your JSON files to find missing translations. Keys present in JSON but never marked as enabled are unused dead entries.


Text

Package: decok.dfcdvadstf.catframe.ui.Text

A text wrapper that supports both literal strings and namespace-based translatable keys — similar to modern Minecraft's Component system. Use it anywhere you'd normally pass a String for display text.

Literal vs Translatable

// Literal — always renders the same text
Text label = Text.literal("Hello World");

// Translatable — resolved at render time via LocalizationManager
private static final Text PAUSED_TEXT = Text.translatable("catframe:menu.paused");

Two Translatable Formats

Like ResourceLocation, you can specify keys in two ways:

// Colon format: "domain:key"
Text title = Text.translatable("catframe:ui.layouts.default");

// Explicit domain + key
Text title2 = Text.translatable("catframe", "ui.layouts.default");

// With format arguments
Text count = Text.translatable("catframe:item.example.count", 5, 10);
Text count2 = Text.translatable("catframe", "item.example.count", 5, 10);

Text.translatable() automatically calls LocalizationManager.KeyTracking.mark() — the key is tracked as "enabled" the moment you create it.

Instance Method: setTranslatable

Already have a Text instance and want to make it translatable later?

Text dyn = new Text();
dyn.setTranslatable("catframe:menu.paused");          // colon format
dyn.setTranslatable("catframe", "menu.paused");       // domain+key format
dyn.setTranslatable("catframe:item.count", 5, 10);   // with args

Resolving the String

Call getString() when you need the final display text. The Text instance is safe to hold as a static final field — translation lookup happens lazily at render time:

private static final Text PAUSED_TEXT = Text.translatable("catframe:menu.paused");

// In drawScreen():
this.drawString(fontRendererObj, PAUSED_TEXT.getString(), x, y, 0xFFFFFF);

// toString() delegates to getString() for convenience
this.drawString(fontRendererObj, PAUSED_TEXT.toString(), x, y, 0xFFFFFF);

API Reference

Constructors:

Method Description
new Text() Create an empty Text
new Text(String literal) Create a literal Text

Static factories:

Method Returns Description
Text.literal(String text) Text Create a non-translatable literal text.
Text.literal(String text, Style style) Text Create a literal text with style.
Text.translatable(String resourceKey, Object... args) Text Create translatable text from domain:key.
Text.translatable(Style style, String resourceKey, Object... args) Text Create translatable text with style.
Text.translatable(String domain, String key, Object... args) Text Create translatable text from explicit domain + key.
Text.translatable(String domain, String key, Style style, Object... args) Text Create translatable text with explicit domain, key, and style.
Text.translatableString(String resourceKey, Object... args) String Translate domain:key directly to String.
Text.translatableString(String domain, String key, Object... args) String Translate domain+key directly to String.
Text.literalString(String text) String Identity helper (returns text as-is).

Instance methods:

Method Returns Description
setTranslatable(String resourceKey, Object... args) void Mark this instance as translatable (domain:key).
setTranslatable(String domain, String key, Object... args) void Mark this instance as translatable (explicit).
setLiteral(String text) void Revert to literal text.
getString() String Resolve to the final display string.
getRaw() String The raw domain:key (if translatable) or literal text.
getDomain() String Domain portion (empty for literals).
getKey() String Key portion or literal text.
getArgs() Object[] Format arguments.
isTranslatable() boolean Whether this Text uses translation.
getStyle() Style Get the associated style (or null).
setStyle(Style style) void Set the style for this Text.
withStyle(Style style) Text Return new Text with specified style.
withStyleApplied(Style style) Text Return new Text with style merged on top of existing.

Note: Text overrides equals(), hashCode(), and toString(). Two Text instances are equal if they share the same domain, key, translatable flag, args, and style — so you can use them in Map lookups or Set deduplication.


LoadingDotsText

Package: decok.dfcdvadstf.catframe.ui.LoadingDotsText

A tiny utility that produces an animated three-frame loading indicator — "o O o""o o O""O o o" — cycling every 200 ms.

// In drawScreen(), pass the current time in milliseconds:
String dots = LoadingDotsText.get(System.currentTimeMillis());
this.drawString(fontRendererObj, "Loading" + dots, x, y, 0xFFFFFF);
Method Returns Description
get(long timeMs) String Get the current animation frame. Cycles every 200 ms.