Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New translation format system (converting json patches) #1492

Draft
wants to merge 10 commits into
base: 1.21.x
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,60 +1,27 @@
--- a/net/minecraft/client/resources/language/ClientLanguage.java
+++ b/net/minecraft/client/resources/language/ClientLanguage.java
@@ -22,36 +_,50 @@
public class ClientLanguage extends Language {
private static final Logger LOGGER = LogUtils.getLogger();
private final Map<String, String> storage;
+ private final Map<String, net.minecraft.network.chat.Component> componentStorage;
private final boolean defaultRightToLeft;

+ @Deprecated
private ClientLanguage(Map<String, String> p_118914_, boolean p_118915_) {
+ this(p_118914_, p_118915_, Map.of());
+ }
+
+ private ClientLanguage(Map<String, String> p_118914_, boolean p_118915_, Map<String, net.minecraft.network.chat.Component> componentStorage) {
this.storage = p_118914_;
this.defaultRightToLeft = p_118915_;
+ this.componentStorage = componentStorage;
}

public static ClientLanguage loadFrom(ResourceManager p_265765_, List<String> p_265743_, boolean p_265470_) {
Map<String, String> map = Maps.newHashMap();
+ Map<String, net.minecraft.network.chat.Component> componentMap = Maps.newHashMap();
@@ -34,6 +_,7 @@

for (String s : p_265743_) {
String s1 = String.format(Locale.ROOT, "lang/%s.json", s);
+ map.putAll(net.neoforged.fml.i18n.I18nManager.loadTranslations(s));

for (String s2 : p_265765_.getNamespaces()) {
try {
ResourceLocation resourcelocation = ResourceLocation.fromNamespaceAndPath(s2, s1);
- appendFrom(s, p_265765_.getResourceStack(resourcelocation), map);
+ appendFrom(s, p_265765_.getResourceStack(resourcelocation), map, componentMap);
} catch (Exception exception) {
LOGGER.warn("Skipped language file: {}:{} ({})", s2, s1, exception.toString());
}
}
}

- return new ClientLanguage(ImmutableMap.copyOf(map), p_265470_);
+ return new ClientLanguage(ImmutableMap.copyOf(map), p_265470_, ImmutableMap.copyOf(componentMap));
}
@@ -60,6 +_,12 @@

+ @Deprecated
private static void appendFrom(String p_235036_, List<Resource> p_235037_, Map<String, String> p_235038_) {
+ appendFrom(p_235036_, p_235037_, p_235038_, new java.util.HashMap<>());
@Override
public String getOrDefault(String p_118920_, String p_265273_) {
+ return net.neoforged.neoforge.common.text.TemplateParser.stripTemplate(p_118920_, getOrDefaultRaw(p_118920_, p_265273_));
+ }
+
+ private static void appendFrom(String p_235036_, List<Resource> p_235037_, Map<String, String> p_235038_, Map<String, net.minecraft.network.chat.Component> componentMap) {
for (Resource resource : p_235037_) {
try (InputStream inputstream = resource.open()) {
- Language.loadFromJson(inputstream, p_235038_::put);
+ Language.loadFromJson(inputstream, p_235038_::put, componentMap::put);
} catch (IOException ioexception) {
LOGGER.warn("Failed to load translations for {} from pack {}", p_235036_, resource.sourcePackId(), ioexception);
}
@@ -76,5 +_,15 @@
+ @Override
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public String getOrDefaultRaw(String p_118920_, String p_265273_) {
return this.storage.getOrDefault(p_118920_, p_265273_);
}

@@ -76,5 +_,10 @@
@Override
public FormattedCharSequence getVisualOrder(FormattedText p_118925_) {
return FormattedBidiReorder.reorder(p_118925_, this.defaultRightToLeft);
Expand All @@ -63,10 +30,5 @@
+ @Override
+ public Map<String, String> getLanguageData() {
+ return storage;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.Nullable net.minecraft.network.chat.Component getComponent(String key) {
+ return componentStorage.get(key);
}
}
93 changes: 48 additions & 45 deletions patches/net/minecraft/locale/Language.java.patch
Original file line number Diff line number Diff line change
@@ -1,82 +1,85 @@
--- a/net/minecraft/locale/Language.java
+++ b/net/minecraft/locale/Language.java
@@ -36,8 +_,10 @@
private static Language loadDefault() {
@@ -37,10 +_,17 @@
Builder<String, String> builder = ImmutableMap.builder();
BiConsumer<String, String> biconsumer = builder::put;
- parseTranslations(biconsumer, "/assets/minecraft/lang/en_us.json");
parseTranslations(biconsumer, "/assets/minecraft/lang/en_us.json");
- final Map<String, String> map = builder.build();
+ Map<String, net.minecraft.network.chat.Component> componentMap = new java.util.HashMap<>();
+ parseTranslations(biconsumer, componentMap::put, "/assets/minecraft/lang/en_us.json");
+ final Map<String, String> map = new java.util.HashMap<>(builder.build());
+ net.neoforged.neoforge.server.LanguageHook.captureLanguageMap(map, componentMap);
+ net.neoforged.neoforge.server.LanguageHook.captureLanguageMap(map);
return new Language() {
@Override
public String getOrDefault(String p_128127_, String p_265421_) {
@@ -64,21 +_,51 @@
+ return net.neoforged.neoforge.common.text.TemplateParser.stripTemplate(p_128127_, getOrDefaultRaw(p_128127_, p_265421_));
+ }
+
+ @Override
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public String getOrDefaultRaw(String p_128127_, String p_265421_) {
return map.getOrDefault(p_128127_, p_265421_);
}

@@ -64,6 +_,11 @@
)
.isPresent();
}
+
+ @Override
+ public Map<String, String> getLanguageData() {
+ return map;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.Nullable net.minecraft.network.chat.Component getComponent(String key) {
+ return componentMap.get(key);
+ }
};
}

+ @Deprecated
private static void parseTranslations(BiConsumer<String, String> p_282031_, String p_283638_) {
+ parseTranslations(p_282031_, (key, value) -> {}, p_283638_);
+ }
+
+ private static void parseTranslations(BiConsumer<String, String> p_282031_, BiConsumer<String, net.minecraft.network.chat.Component> componentConsumer, String p_283638_) {
try (InputStream inputstream = Language.class.getResourceAsStream(p_283638_)) {
- loadFromJson(inputstream, p_282031_);
+ loadFromJson(inputstream, p_282031_, componentConsumer);
} catch (JsonParseException | IOException ioexception) {
LOGGER.error("Couldn't read strings from {}", p_283638_, ioexception);
}
}

public static void loadFromJson(InputStream p_128109_, BiConsumer<String, String> p_128110_) {
+ loadFromJson(p_128109_, p_128110_, (key, value) -> {});
+ }
+
+ public static void loadFromJson(InputStream p_128109_, BiConsumer<String, String> p_128110_, BiConsumer<String, net.minecraft.network.chat.Component> componentConsumer) {
@@ -79,6 +_,11 @@
JsonObject jsonobject = GSON.fromJson(new InputStreamReader(p_128109_, StandardCharsets.UTF_8), JsonObject.class);

for (Entry<String, JsonElement> entry : jsonobject.entrySet()) {
+ if (entry.getValue().isJsonArray()) {
+ var component = net.minecraft.network.chat.ComponentSerialization.CODEC
+ .parse(com.mojang.serialization.JsonOps.INSTANCE, entry.getValue())
+ .getOrThrow(msg -> new com.google.gson.JsonParseException("Error parsing translation for " + entry.getKey() + ": " + msg));
+
+ p_128110_.accept(entry.getKey(), component.getString());
+ componentConsumer.accept(entry.getKey(), component);
+
+ p_128110_.accept(entry.getKey(), net.neoforged.neoforge.common.text.JsonTemplateParser.reencodeJson(entry.getValue()));
+ continue;
+ }
+
String s = UNSUPPORTED_FORMAT_PATTERN.matcher(GsonHelper.convertToString(entry.getValue(), entry.getKey())).replaceAll("%$1s");
p_128110_.accept(entry.getKey(), s);
}
@@ -90,6 +_,13 @@

public static void inject(Language p_128115_) {
@@ -92,12 +_,21 @@
instance = p_128115_;
+ }
+
}
+ // Neo: All helpers methods below are injected by Neo to ease modder's usage of Language
+ public Map<String, String> getLanguageData() { return ImmutableMap.of(); }
+
public String getOrDefault(String p_128111_) {
return this.getOrDefault(p_128111_, p_128111_);
}

public abstract String getOrDefault(String p_265702_, String p_265599_);

+ // Neo: Bypass for the formatter
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public String getOrDefaultRaw(String p_265702_, String p_265599_) {
+ return getOrDefault(p_265702_, p_265599_);
+ }
+
public abstract boolean has(String p_128117_);

public abstract boolean isDefaultRightToLeft();
@@ -106,5 +_,17 @@

public List<FormattedCharSequence> getVisualOrder(List<FormattedText> p_128113_) {
return p_128113_.stream().map(this::getVisualOrder).collect(ImmutableList.toImmutableList());
+ }
+
+ // Neo: For API stability in 1.21(.1) only, does nothing
+ @Deprecated(forRemoval = true)
+ public @org.jetbrains.annotations.Nullable net.minecraft.network.chat.Component getComponent(String key) {
+ return null;
+ }
+
+ // Neo: For API stability in 1.21(.1) only, extra parameter does nothing
+ @Deprecated(forRemoval = true)
+ public static void loadFromJson(InputStream p_128109_, BiConsumer<String, String> p_128110_, BiConsumer<String, net.minecraft.network.chat.Component> componentConsumer) {
+ loadFromJson(p_128109_, p_128110_);
}

public String getOrDefault(String p_128111_) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,17 @@
}

@Override
@@ -92,6 +_,13 @@
@@ -92,10 +_,12 @@
Language language = Language.getInstance();
if (language != this.decomposedWith) {
this.decomposedWith = language;
- String s = this.fallback != null ? language.getOrDefault(this.key, this.fallback) : language.getOrDefault(this.key);
+
+ Component langComponent = language.getComponent(this.key);
+ if (langComponent != null) {
+ this.decomposedParts = ImmutableList.of(langComponent);
+ return;
+ }
+
String s = this.fallback != null ? language.getOrDefault(this.key, this.fallback) : language.getOrDefault(this.key);
+ String s = this.fallback != null ? language.getOrDefaultRaw(this.key, this.fallback) : language.getOrDefaultRaw(this.key, this.key);

try {
@@ -170,6 +_,12 @@
public <T> Optional<T> visit(FormattedText.StyledContentConsumer<T> p_237521_, Style p_237522_) {
this.decompose();

+ if (!net.neoforged.neoforge.common.util.InsertingContents.pushTranslation(this)) {
+ // Reference cycle.
+ return Optional.empty();
+ }
+
+ try {
for (FormattedText formattedtext : this.decomposedParts) {
Optional<T> optional = formattedtext.visit(p_237521_, p_237522_);
if (optional.isPresent()) {
@@ -178,12 +_,21 @@
}

return Optional.empty();
+ } finally {
+ net.neoforged.neoforge.common.util.InsertingContents.popTranslation();
+ }
}

@Override
public <T> Optional<T> visit(FormattedText.ContentConsumer<T> p_237519_) {
this.decompose();

+ if (!net.neoforged.neoforge.common.util.InsertingContents.pushTranslation(this)) {
+ // Reference cycle.
+ return Optional.empty();
+ }
+
+ try {
for (FormattedText formattedtext : this.decomposedParts) {
Optional<T> optional = formattedtext.visit(p_237519_);
if (optional.isPresent()) {
@@ -192,6 +_,9 @@
}

return Optional.empty();
+ } finally {
+ net.neoforged.neoforge.common.util.InsertingContents.popTranslation();
+ }
}

@Override
Builder<FormattedText> builder = ImmutableList.builder();
+ s = net.neoforged.neoforge.common.text.TemplateParser.decomposeTemplate(this, this.args, s, builder::add);
this.decomposeTemplate(s, builder::add);
this.decomposedParts = builder.build();
} catch (TranslatableFormatException translatableformatexception) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.text;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.mojang.serialization.JsonOps;
import java.util.function.Consumer;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.MutableComponent;
import net.neoforged.neoforge.common.util.InsertingContents;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public class JsonTemplateParser {
protected static final Gson GSON = new Gson();

public static String handle(final String template, final Object[] args, final Consumer<FormattedText> consumer) {
try {
process(parse(GSON.fromJson(template, JsonArray.class)), args, consumer);
return "";
} catch (JsonParseException e) {
throw new TemplateParser.ParsingException(e.getMessage());
}
}

private static void process(final Component component, final Object[] args, final Consumer<FormattedText> consumer) {
if (component.getContents() instanceof InsertingContents icon) {
consumer.accept(getArgument(args, icon.index()).withStyle(component.getStyle()));
} else if (!component.getSiblings().isEmpty()) {
consumer.accept(MutableComponent.create(component.getContents()).withStyle(component.getStyle()));
} else {
consumer.accept(component);
}
for (final Component sibling : component.getSiblings()) {
process(sibling, args, consumer);
}
}

public static String handle(final String template) {
try {
return parse(GSON.fromJson(template, JsonArray.class)).getString();
} catch (JsonParseException e) {
throw new TemplateParser.ParsingException(e.getMessage());
}
}

private static Component parse(JsonElement element) {
return ComponentSerialization.CODEC
.parse(JsonOps.INSTANCE, element)
.getOrThrow(msg -> new JsonParseException("Error parsing json: " + msg));
}

private static MutableComponent getArgument(final Object[] args, final int index) {
if (index >= 0 && index < args.length) {
final Object object = args[index];
if (object instanceof MutableComponent) {
return (MutableComponent) object;
} else if (object instanceof Component) {
return Component.empty().append((Component) object);
} else if (object == null) {
return Component.literal("null");
} else {
return Component.literal(object.toString());
}
} else {
throw new TemplateParser.ParsingException("Invalid index: " + index);
}
}

public static String reencodeJson(JsonElement element) {
return TemplateParser.JSON_MARKER + GSON.toJson(element);
}
}
Loading
Loading