Skip to content

Commit 123c530

Browse files
Added language support for Chinese.
1 parent eb3f8f1 commit 123c530

File tree

7 files changed

+7327
-13
lines changed

7 files changed

+7327
-13
lines changed

src/main/kotlin/NoMathExpectation/chatExchange/neoForged/ChatExchangeConfig.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ object ChatExchangeConfig {
2929
.translation("modid.config.token")
3030
.worldRestart()
3131
.define("token", "")
32+
val language: ModConfigSpec.ConfigValue<String> = builder.comment("The language the exchange server messages will be.", "Leave blank to use the language the game is using.")
33+
.translation("modid.config.language")
34+
.worldRestart()
35+
.define("language", "")
3236

3337
val spec = builder.build()
3438

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package NoMathExpectation.chatExchange.neoForged
2+
3+
import net.minecraft.locale.Language
4+
import net.minecraft.network.chat.Component
5+
import net.minecraft.network.chat.FormattedText
6+
import net.minecraft.network.chat.Style
7+
import net.minecraft.resources.ResourceLocation
8+
import net.minecraft.server.MinecraftServer
9+
import net.minecraft.server.packs.PackType
10+
import net.minecraft.server.packs.resources.MultiPackResourceManager
11+
import net.minecraft.util.FormattedCharSequence
12+
import net.minecraft.util.StringDecomposer
13+
import net.neoforged.fml.i18n.I18nManager
14+
import org.apache.logging.log4j.LogManager
15+
import java.util.*
16+
17+
class CustomLanguage(
18+
private val textMap: Map<String, String>,
19+
private val componentMap: Map<String, Component>,
20+
private val defaultRightToLeft: Boolean = false,
21+
) : Language() {
22+
override fun getOrDefault(key: String, defaultValue: String) = textMap.getOrDefault(key, defaultValue)
23+
24+
override fun has(id: String) = id in textMap
25+
26+
override fun isDefaultRightToLeft() = defaultRightToLeft
27+
28+
override fun getVisualOrder(text: FormattedText) = FormattedCharSequence {
29+
text.visit(
30+
{ style, string ->
31+
if (StringDecomposer.iterateFormatted(
32+
string,
33+
style,
34+
it
35+
)
36+
) Optional.empty() else FormattedText.STOP_ITERATION
37+
},
38+
Style.EMPTY
39+
).isPresent
40+
}
41+
42+
override fun getLanguageData() = textMap
43+
44+
override fun getComponent(key: String) = componentMap[key]
45+
}
46+
47+
private val logger = LogManager.getLogger(ChatExchange.ID)
48+
49+
fun languageOf(lang: String, server: MinecraftServer): Language {
50+
val textMap = mutableMapOf<String, String>()
51+
val componentMap = mutableMapOf<String, Component>()
52+
53+
CustomLanguage::class.java.getResourceAsStream("/assets/chatexchange/mclang/$lang.json")?.use {
54+
Language.loadFromJson(it, textMap::put, componentMap::put)
55+
}
56+
57+
CustomLanguage::class.java.getResourceAsStream("/assets/chatexchange/neolang/$lang.json")?.use {
58+
Language.loadFromJson(it, textMap::put, componentMap::put)
59+
}
60+
61+
textMap += I18nManager.loadTranslations(lang)
62+
63+
val langFile = String.format(Locale.ROOT, "lang/%s.json", lang)
64+
val resourceManager = server.serverResources.resourceManager
65+
val clientResources = MultiPackResourceManager(PackType.CLIENT_RESOURCES, resourceManager.listPacks().toList())
66+
val loaded = clientResources.namespaces.map { namespace ->
67+
runCatching {
68+
val langResource = ResourceLocation.fromNamespaceAndPath(namespace, langFile)
69+
clientResources.getResourceStack(langResource).forEach { resource ->
70+
resource.open().use {
71+
Language.loadFromJson(it, textMap::put, componentMap::put)
72+
}
73+
}
74+
}.onFailure {
75+
logger.warn("Skipped language file: {}:{}", namespace, langFile, it)
76+
return@map 0
77+
}
78+
1
79+
}.sum()
80+
logger.debug("Loaded {} language files for {}", loaded, lang)
81+
82+
return CustomLanguage(textMap, componentMap)
83+
}
84+
85+
fun languageOfOrDefault(lang: String, server: MinecraftServer): Language = runCatching {
86+
languageOf(lang, server)
87+
}.getOrElse {
88+
logger.error("Failed to load language: $lang", it)
89+
Language.getInstance()
90+
}
91+
92+
fun Component.getStringWithLanguage(language: Language): String {
93+
val current = Language.getInstance()
94+
Language.inject(language)
95+
val result = string
96+
Language.inject(current)
97+
return result
98+
}

src/main/kotlin/NoMathExpectation/chatExchange/neoForged/ExchangeServer.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import io.ktor.utils.io.*
66
import kotlinx.coroutines.*
77
import kotlinx.coroutines.sync.Mutex
88
import kotlinx.coroutines.sync.withLock
9+
import net.minecraft.locale.Language
910
import net.minecraft.network.chat.Component
1011
import net.minecraft.server.MinecraftServer
1112
import org.apache.logging.log4j.LogManager
1213

13-
class ExchangeServer(private val minecraftServer: MinecraftServer) : CoroutineScope {
14+
class ExchangeServer(
15+
private val minecraftServer: MinecraftServer,
16+
private val language: Language,
17+
) : CoroutineScope {
1418
private val scope = CoroutineScope(Dispatchers.IO) + CoroutineName("ChatExchangeServer")
1519
override val coroutineContext get() = scope.coroutineContext
1620

@@ -89,27 +93,26 @@ class ExchangeServer(private val minecraftServer: MinecraftServer) : CoroutineSc
8993
if (channel.isClosedForRead) {
9094
return
9195
}
92-
93-
logger.error("Failed to receive message from a client.")
94-
logger.error(it)
96+
logger.error("Failed to receive message from a client.", it)
9597
}
9698
}
9799
}
98100

99-
private suspend fun sendEvent(event: ExchangeEvent) {
101+
suspend fun sendEvent(event: ExchangeEvent) {
100102
logger.info("Sending event: $event")
101103
channelMutex.withLock {
102104
sendChannels.forEach {
103105
kotlin.runCatching {
104106
it.writeExchangeEvent(event)
105107
}.onFailure {
106-
logger.error("Failed to send message to a client.")
107-
logger.error(it)
108+
logger.error("Failed to send message to a client.", it)
108109
}
109110
}
110111
}
111112
}
112113

114+
fun componentToString(component: Component) = component.getStringWithLanguage(language)
115+
113116
fun launch() {
114117
if (launched) {
115118
error("Server is already launched!")
@@ -127,7 +130,15 @@ class ExchangeServer(private val minecraftServer: MinecraftServer) : CoroutineSc
127130

128131
fun startNewInstance(server: MinecraftServer) {
129132
instance?.cancel()
130-
instance = ExchangeServer(server)
133+
134+
val languageName = ChatExchangeConfig.language.get()
135+
val language = if (languageName.isNotBlank()) {
136+
languageOfOrDefault(languageName, server)
137+
} else {
138+
Language.getInstance()
139+
}
140+
141+
instance = ExchangeServer(server, language)
131142
instance?.launch()
132143
}
133144

@@ -143,5 +154,7 @@ class ExchangeServer(private val minecraftServer: MinecraftServer) : CoroutineSc
143154
instance.sendEvent(event)
144155
}
145156
}
157+
158+
fun componentToString(component: Component): String = instance?.componentToString(component) ?: component.string
146159
}
147160
}

src/main/kotlin/NoMathExpectation/chatExchange/neoForged/NeoForgeEvents.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ object NeoForgeEvents {
5353
val player = event.entity as? Player ?: return
5454
val cause = event.source
5555

56-
val text = cause.getLocalizedDeathMessage(player).string
56+
val playerName = ExchangeServer.componentToString(player.name)
57+
val text = ExchangeServer.componentToString(cause.getLocalizedDeathMessage(player))
5758
ExchangeServer.sendEvent(
58-
PlayerDieEvent(player.name.string, text)
59+
PlayerDieEvent(playerName, text)
5960
)
6061
}
6162

@@ -67,9 +68,11 @@ object NeoForgeEvents {
6768
return
6869
}
6970

70-
val advancementName = advancement.display.getOrNull()?.title?.string ?: return
71+
val advancementName =
72+
advancement.display.getOrNull()?.title?.let { ExchangeServer.componentToString(it) } ?: return
73+
val playerName = ExchangeServer.componentToString(player.name)
7174
ExchangeServer.sendEvent(
72-
PlayerAdvancementEvent(player.name.string, advancementName)
75+
PlayerAdvancementEvent(playerName, advancementName)
7376
)
7477
}
7578
}

src/main/resources/assets/chatexchange/lang/en_us.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"itemGroup.chatexchange": "Chat Exchange",
33
"modid.config.host": "Host",
44
"modid.config.port": "Port",
5-
"modid.config.token": "Token"
5+
"modid.config.token": "Token",
6+
"modid.config.language": "Language"
67
}

0 commit comments

Comments
 (0)