User: moulberry Date: 03 May 24 09:26 Revision: 336924bed50a7b31ab01929da3477c5742cf9b02 Summary: Translation: option to force json and configurable default i18n call TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9361&personal=false Index: src/main/kotlin/TranslationSettings.kt =================================================================== --- src/main/kotlin/TranslationSettings.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) +++ src/main/kotlin/TranslationSettings.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -0,0 +1,71 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project + +@State(name = "TranslationSettings", storages = [Storage("minecraft_dev.xml")]) +class TranslationSettings : PersistentStateComponent { + + data class State( + var isForceJsonTranslationFile: Boolean = false, + var isUseCustomConvertToTranslationTemplate: Boolean = false, + var convertToTranslationTemplate: String = "net.minecraft.client.resources.I18n.format(\"\$key\")", + ) + + private var state = State() + + override fun getState(): State { + return state + } + + override fun loadState(state: State) { + this.state = state + } + + // State mappings + var isForceJsonTranslationFile: Boolean + get() = state.isForceJsonTranslationFile + set(forceJsonTranslationFile) { + state.isForceJsonTranslationFile = forceJsonTranslationFile + } + + var isUseCustomConvertToTranslationTemplate: Boolean + get() = state.isUseCustomConvertToTranslationTemplate + set(useCustomConvertToTranslationTemplate) { + state.isUseCustomConvertToTranslationTemplate = useCustomConvertToTranslationTemplate + } + + var convertToTranslationTemplate: String + get() = state.convertToTranslationTemplate + set(convertToTranslationTemplate) { + state.convertToTranslationTemplate = convertToTranslationTemplate + } + + companion object { + @JvmStatic + fun getInstance(project: Project): TranslationSettings = project.service() + } +} Index: src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt =================================================================== --- src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -43,6 +43,15 @@ owner = "net.minecraft.network.chat.Component", name = "translatableEscape", descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/MutableComponent;" + ), + MemberReference( + owner = "net.minecraft.client.resource.language.I18n", + name = "translate", + descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" + ) mapTo MemberReference( + owner = "net.minecraft.client.resources.language.I18n", + name = "get", + descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" ) ), hashMapOf(), Index: src/main/kotlin/platform/mcp/mappings/Mappings.kt =================================================================== --- src/main/kotlin/platform/mcp/mappings/Mappings.kt (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/kotlin/platform/mcp/mappings/Mappings.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -119,6 +119,13 @@ return getMappedMethod(MemberReference(mojangMethod, mojangDescriptor, mojangClass)) } +fun Module.getMappedMethodCall(mojangClass: String, mojangMethod: String, mojangDescriptor: String, p: String): String { + val mappedMethodRef = namedToMojang?.tryGetMappedMethod( + MemberReference(mojangMethod, mojangDescriptor, mojangClass) + ) ?: return "$mojangClass.$mojangMethod($p)" + return "${mappedMethodRef.owner}.${mappedMethodRef.name}($p)" +} + fun Module.getMojangMethod(mappedMethod: MemberReference): String { return namedToMojang?.getIntermediaryMethod(mappedMethod)?.name ?: return mappedMethod.name } Index: src/main/kotlin/translations/TranslationFiles.kt =================================================================== --- src/main/kotlin/translations/TranslationFiles.kt (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/kotlin/translations/TranslationFiles.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -20,6 +20,7 @@ package com.demonwav.mcdev.translations +import com.demonwav.mcdev.TranslationSettings import com.demonwav.mcdev.translations.index.TranslationIndex import com.demonwav.mcdev.translations.index.TranslationInverseIndex import com.demonwav.mcdev.translations.lang.LangFile @@ -110,12 +111,43 @@ element.delete() } + fun findTranslationKeyForText(context: PsiElement, text: String): String? { + val module = context.findModule() + ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + jsonVersion = version > MC_1_12_2 + } + + if (!jsonVersion) { + // This feature only supports JSON translation files + return null + } + + val files = FileTypeIndex.getFiles( + JsonFileType.INSTANCE, + GlobalSearchScope.moduleScope(module), + ).filter { getLocale(it) == TranslationConstants.DEFAULT_LOCALE } + + for (file in files) { + val psiFile = PsiManager.getInstance(context.project).findFile(file) ?: continue + psiFile.findKeyForTextAsJson(text)?.let { return it } + } + + return null + } + fun add(context: PsiElement, key: String, text: String) { val module = context.findModule() ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { - val version = - context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") - val jsonVersion = version > MC_1_12_2 + jsonVersion = version > MC_1_12_2 + } fun write(files: Iterable) { for (file in files) { @@ -223,6 +255,13 @@ doc.insertString(rootObject.lastChild.prevSibling.textOffset, content) } + private fun PsiFile.findKeyForTextAsJson(text: String): String? { + val rootObject = this.firstChild as? JsonObject ?: return null + return rootObject.propertyList.firstOrNull { + (it.value as? JsonStringLiteral)?.value == text + }?.name + } + private fun generateJsonFile( leadingComma: Boolean, indent: CharSequence, @@ -292,9 +331,12 @@ fun buildSortingTemplateFromDefault(context: PsiElement, domain: String? = null): Template? { val module = context.findModule() ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { - val version = - context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") - val jsonVersion = version > MC_1_12_2 + jsonVersion = version > MC_1_12_2 + } val defaultTranslationFile = FileBasedIndex.getInstance() .getContainingFiles( Index: src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt =================================================================== --- src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -20,7 +20,10 @@ package com.demonwav.mcdev.translations.intentions +import com.demonwav.mcdev.TranslationSettings +import com.demonwav.mcdev.platform.mcp.mappings.getMappedMethodCall import com.demonwav.mcdev.translations.TranslationFiles +import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.runWriteAction import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction import com.intellij.lang.java.JavaLanguage @@ -42,6 +45,9 @@ override fun invoke(project: Project, editor: Editor, element: PsiElement) { if (element.parent is PsiLiteral) { val value = (element.parent as PsiLiteral).value as? String ?: return + + val existingKey = TranslationFiles.findTranslationKeyForText(element, value) + val result = Messages.showInputDialogWithCheckBox( "Enter translation key:", "Convert String Literal to Translation", @@ -49,7 +55,7 @@ true, true, Messages.getQuestionIcon(), - null, + existingKey, object : InputValidatorEx { override fun getErrorText(inputString: String): String? { if (inputString.isEmpty()) { @@ -73,12 +79,24 @@ val key = result.first ?: return val replaceLiteral = result.second try { + if (existingKey != key) { - TranslationFiles.add(element, key, value) + TranslationFiles.add(element, key, value) + } if (replaceLiteral) { + val translationSettings = TranslationSettings.getInstance(project) val psi = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return psi.runWriteAction { val expression = JavaPsiFacade.getElementFactory(project).createExpressionFromText( - "net.minecraft.client.resources.I18n.format(\"$key\")", + if (translationSettings.isUseCustomConvertToTranslationTemplate) { + translationSettings.convertToTranslationTemplate.replace("\$key", key) + } else { + element.findModule()?.getMappedMethodCall( + "net.minecraft.client.resource.language.I18n", + "translate", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + "\"$key\"" + ) ?: "net.minecraft.client.resource.I18n.get(\"$key\")" + }, element.context, ) if (psi.language === JavaLanguage.INSTANCE) { Index: src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt =================================================================== --- src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -20,6 +20,7 @@ package com.demonwav.mcdev.translations.sorting +import com.demonwav.mcdev.TranslationSettings import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.translations.lang.colors.LangSyntaxHighlighter import com.intellij.codeInsight.template.impl.TemplateEditorUtil @@ -32,7 +33,13 @@ import com.intellij.openapi.options.Configurable import com.intellij.openapi.project.Project import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.COLUMNS_LARGE +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.columns import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.selected +import com.intellij.ui.layout.ComponentPredicate import com.intellij.util.ui.JBUI import java.awt.BorderLayout import javax.swing.DefaultComboBoxModel @@ -46,7 +53,7 @@ private var templateEditor: Editor? = null private val editorPanel = JPanel(BorderLayout()).apply { - preferredSize = JBUI.size(250, 450) + preferredSize = JBUI.size(250, 350) minimumSize = preferredSize } @@ -62,8 +69,27 @@ row { cell(editorPanel).align(Align.FILL) } + + val translationSettings = TranslationSettings.getInstance(project) + row { + checkBox(MCDevBundle("minecraft.settings.translation.force_json_translation_file")) + .bindSelected(translationSettings::isForceJsonTranslationFile) - } + } + lateinit var allowConvertToTranslationTemplate: ComponentPredicate + row { + val checkBox = checkBox(MCDevBundle("minecraft.settings.translation.use_custom_convert_template")) + .bindSelected(translationSettings::isUseCustomConvertToTranslationTemplate) + allowConvertToTranslationTemplate = checkBox.selected + } + + row { + textField().bindText(translationSettings::convertToTranslationTemplate) + .enabledIf(allowConvertToTranslationTemplate) + .columns(COLUMNS_LARGE) + } + } + @Nls override fun getDisplayName() = MCDevBundle("minecraft.settings.lang_template.display_name") @@ -107,7 +133,7 @@ } override fun isModified(): Boolean { - return templateEditor?.document?.text != getActiveTemplateText() != false + return templateEditor?.document?.text != getActiveTemplateText() != false || panel.isModified() } override fun apply() { @@ -120,9 +146,12 @@ } else if (project != null) { TemplateManager.writeProjectTemplate(project, editor.document.text) } + + panel.apply() } override fun reset() { init() + panel.reset() } } Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/resources/META-INF/plugin.xml (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -285,6 +285,7 @@ + Index: src/main/resources/messages/MinecraftDevelopment.properties =================================================================== --- src/main/resources/messages/MinecraftDevelopment.properties (revision caca374c623d9f3315f3c9f9fc3e40597c40b28f) +++ src/main/resources/messages/MinecraftDevelopment.properties (revision 336924bed50a7b31ab01929da3477c5742cf9b02) @@ -213,3 +213,6 @@ minecraft.settings.lang_template.comment=You may edit the template used for translation key sorting here.\
Each line may be empty, a comment (with #) or a glob pattern for matching translation keys (like "item.*").\
Note: Empty lines are respected and will be put into the sorting result. +minecraft.settings.translation=Translation +minecraft.settings.translation.force_json_translation_file=Force JSON translation file (1.13+) +minecraft.settings.translation.use_custom_convert_template=Use custom template for convert literal to translation