HMCL-dev/HMCL · error · JsonParseException

Localized text values must be strings

Error message

Localized text values must be strings: ${field}

What it means

Thrown by parseLocalizedText when a localized text object contains a value that is not a JSON string. Each locale key in the localized object must map to a string; numbers, booleans, nested objects, arrays, or null values are rejected, with the field name in the message.

Solutions

  1. Quote every value in the localized object so all values are JSON strings.
  2. Remove or fix non-string entries; do not nest objects inside localized text.
  3. Add a lint step that checks localized maps for string-only values.

Example fix

// before
"name": { "en": "My Theme", "zh": 123 }
// after
"name": { "en": "My Theme", "zh": "我的主题" }
Defensive patterns

Strategy: validation

Validate before calling

JsonObject name = manifestJson.getAsJsonObject("name");
for (Map.Entry<String, JsonElement> e : name.entrySet()) {
    if (!(e.getValue() instanceof JsonPrimitive p && p.isString())) {
        throw new IllegalArgumentException("locale '" + e.getKey() + "' must map to a string");
    }
}

Type guard

static boolean isStringValuedMap(JsonObject localized) {
    for (JsonElement v : localized.values()) {
        if (!(v instanceof JsonPrimitive p) || !p.isString()) return false;
    }
    return true;
}

Try / catch

try {
    ThemePackManifest pack = gson.fromJson(json, ThemePackManifest.class);
} catch (JsonParseException e) {
    // highlight the localized field named in the message
}

Prevention

When it happens

Trigger: Deserializing a manifest where a localized object has a non-string value, e.g. "name": { "en": "My Theme", "zh": 123 } or "description": { "en": { "text": "x" } }.

Common situations: A translation tool exported counts or flags as numbers; an author accidentally nested locale maps; a script interpolated a numeric variable as the translation value.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/82fccfd6acaec33c. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManifest.java:216

        }
        return requireNonBlank(primitive.getAsString(), fieldName);
    }

    /// Parses a localized text value.
    static LocalizedText parseLocalizedText(JsonElement element, String field) {
        if (element instanceof JsonPrimitive primitive && primitive.isString()) {
            return LocalizedText.plain(requireNonBlank(primitive.getAsString(), field));
        }
        if (element instanceof JsonObject localizedObject) {
            if (localizedObject.isEmpty()) {
                throw new JsonParseException("Localized text field is empty: " + field);
            }

            LinkedHashMap<String, String> localizedValues = new LinkedHashMap<>();
            for (Map.Entry<String, JsonElement> entry : localizedObject.entrySet()) {
                JsonElement value = entry.getValue();
                if (!(value instanceof JsonPrimitive primitive) || !primitive.isString()) {
                    throw new JsonParseException("Localized text values must be strings: " + field);
                }
                localizedValues.put(
                        requireNonBlank(entry.getKey(), field),
                        requireNonBlank(primitive.getAsString(), field));
            }
            return new LocalizedText(localizedValues);
        }
        throw new JsonParseException("Theme-pack localized text must be a string or object: " + field);
    }

    /// Returns a validated localized text value.
    static LocalizedText requireLocalizedText(LocalizedText value, String field) {
        Objects.requireNonNull(value);

        JsonElement element = JsonUtils.GSON.toJsonTree(value, LocalizedText.class);
        return parseLocalizedText(element, field);
    }

View on GitHub (pinned to 24702dc5a0)