Skip to content

Commit b13eb25

Browse files
committed
Switch to using the @translatable annotation instead of a hardcoded list
1 parent d0bb7ff commit b13eb25

File tree

8 files changed

+123
-415
lines changed

8 files changed

+123
-415
lines changed

src/main/kotlin/translations/TranslationConstants.kt

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,9 @@ package com.demonwav.mcdev.translations
2222

2323
object TranslationConstants {
2424
const val DEFAULT_LOCALE = "en_us"
25-
const val I18N_CLASS = "net.minecraft.client.resources.language.I18n"
26-
const val TRANSLATABLE_COMPONENT = "net.minecraft.network.chat.TranslatableComponent"
27-
const val KEY_MAPPING = "net.minecraft.client.KeyMapping"
28-
const val INPUT_CONSTANTS_KEY = "com.mojang.blaze3d.platform.InputConstants"
29-
const val TEXT_COMPONENT_HELPER = "net.minecraftforge.server.command.TextComponentHelper"
30-
const val CONSTRUCTOR = "<init>"
31-
const val COMMAND_EXCEPTION_CLASS = "net.minecraft.command.CommandException"
32-
const val GET = "m_118938_"
33-
const val EXISTS = "m_118936_"
34-
const val INPUT_CONSTANTS_KEY_GET_KEY = "m_84851_"
35-
const val CREATE_COMPONENT_TRANSLATION = "createComponentTranslation"
36-
const val COMPONENT_CLASS = "net.minecraft.network.chat.Component"
37-
const val COMPONENT_TRANSLATABLE = "translatable"
38-
const val COMPONENT_TRANSLATABLE_WITH_FALLBACK = "translatableWithFallback"
25+
const val TRANSLATABLE_ANNOTATION = "com.demonwav.mcdev.annotations.Translatable"
26+
const val REQUIRED = "required"
27+
const val FOLD_METHOD = "foldMethod"
28+
const val PREFIX = "prefix"
29+
const val SUFFIX = "suffix"
3930
}

src/main/kotlin/translations/folding.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ class TranslationFoldingBuilder : FoldingBuilderEx() {
9999
} else {
100100
foldingElement.textRange
101101
}
102+
if (!translation.required && translation.formattingError != null) {
103+
continue
104+
}
102105
descriptors.add(
103106
FoldingDescriptor(
104107
translation.foldingElement.node,

src/main/kotlin/translations/identification/TranslationFunction.kt

Lines changed: 0 additions & 105 deletions
This file was deleted.

src/main/kotlin/translations/identification/TranslationIdentifier.kt

Lines changed: 111 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,25 @@
2020

2121
package com.demonwav.mcdev.translations.identification
2222

23+
import com.demonwav.mcdev.translations.TranslationConstants
2324
import com.demonwav.mcdev.translations.identification.TranslationInstance.Companion.FormattingError
2425
import com.demonwav.mcdev.translations.index.TranslationIndex
2526
import com.demonwav.mcdev.translations.index.merge
27+
import com.demonwav.mcdev.util.constantStringValue
28+
import com.demonwav.mcdev.util.constantValue
29+
import com.demonwav.mcdev.util.extractVarArgs
30+
import com.demonwav.mcdev.util.referencedMethod
31+
import com.intellij.codeInspection.dataFlow.CommonDataflow
2632
import com.intellij.openapi.project.Project
33+
import com.intellij.psi.CommonClassNames
34+
import com.intellij.psi.PsiCall
2735
import com.intellij.psi.PsiCallExpression
2836
import com.intellij.psi.PsiElement
37+
import com.intellij.psi.PsiEllipsisType
2938
import com.intellij.psi.PsiExpression
3039
import com.intellij.psi.PsiExpressionList
40+
import com.intellij.psi.PsiMethod
41+
import java.util.IllegalFormatException
3142
import java.util.MissingFormatArgumentException
3243

3344
abstract class TranslationIdentifier<T : PsiElement> {
@@ -49,57 +60,108 @@ abstract class TranslationIdentifier<T : PsiElement> {
4960
container: PsiElement,
5061
referenceElement: PsiElement,
5162
): TranslationInstance? {
52-
if (container is PsiExpressionList && container.parent is PsiCallExpression) {
53-
val call = container.parent as PsiCallExpression
54-
val index = container.expressions.indexOf(element)
55-
56-
for (function in TranslationInstance.translationFunctions) {
57-
if (function.matches(call, index)) {
58-
val translationKey = function.getTranslationKey(call, referenceElement) ?: continue
59-
val entries = TranslationIndex.getAllDefaultEntries(project).merge("")
60-
val translation = entries[translationKey]?.text
61-
if (translation != null) {
62-
val foldingElement = when (function.foldParameters) {
63-
TranslationFunction.FoldingScope.CALL -> call
64-
TranslationFunction.FoldingScope.PARAMETER -> element
65-
TranslationFunction.FoldingScope.PARAMETERS -> container
66-
}
67-
try {
68-
val (formatted, superfluousParams) = function.format(translation, call)
69-
?: (translation to -1)
70-
return TranslationInstance(
71-
foldingElement,
72-
function.matchedIndex,
73-
referenceElement,
74-
TranslationInstance.Key(translationKey),
75-
formatted,
76-
if (superfluousParams >= 0) FormattingError.SUPERFLUOUS else null,
77-
superfluousParams,
78-
)
79-
} catch (ignored: MissingFormatArgumentException) {
80-
return TranslationInstance(
81-
foldingElement,
82-
function.matchedIndex,
83-
referenceElement,
84-
TranslationInstance.Key(translationKey),
85-
translation,
86-
FormattingError.MISSING,
87-
)
88-
}
89-
} else {
90-
return TranslationInstance(
91-
null,
92-
function.matchedIndex,
93-
referenceElement,
94-
TranslationInstance.Key(translationKey),
95-
null,
96-
)
97-
}
98-
}
99-
}
63+
if (container !is PsiExpressionList) {
10064
return null
10165
}
102-
return null
66+
val call = container.parent as? PsiCallExpression ?: return null
67+
val index = container.expressions.indexOf(element)
68+
69+
val method = call.referencedMethod ?: return null
70+
val parameter = method.parameterList.getParameter(index) ?: return null
71+
val translatableAnnotation = parameter.getAnnotation(TranslationConstants.TRANSLATABLE_ANNOTATION)
72+
?: return null
73+
74+
val prefix =
75+
translatableAnnotation.findAttributeValue(TranslationConstants.PREFIX)?.constantStringValue ?: ""
76+
val suffix =
77+
translatableAnnotation.findAttributeValue(TranslationConstants.SUFFIX)?.constantStringValue ?: ""
78+
val required =
79+
translatableAnnotation.findAttributeValue(TranslationConstants.REQUIRED)?.constantValue as? Boolean
80+
?: true
81+
82+
val translationKey = CommonDataflow.computeValue(element) as? String ?: return null
83+
84+
val entries = TranslationIndex.getAllDefaultEntries(project).merge("")
85+
val translation = entries[prefix + translationKey + suffix]?.text
86+
?: return TranslationInstance( // translation doesn't exist
87+
null,
88+
index,
89+
referenceElement,
90+
TranslationInstance.Key(prefix, translationKey, suffix),
91+
null,
92+
required
93+
)
94+
95+
val foldMethod =
96+
translatableAnnotation.findAttributeValue(TranslationConstants.FOLD_METHOD)?.constantValue as? Boolean
97+
?: false
98+
99+
val formatting =
100+
(method.parameterList.parameters.last().type as? PsiEllipsisType)
101+
?.componentType?.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) == true
102+
103+
val foldingElement = if (foldMethod) {
104+
call
105+
} else if (
106+
index == 0 &&
107+
container.expressionCount > 1 &&
108+
method.parameterList.parametersCount == 2 &&
109+
formatting
110+
) {
111+
container
112+
} else {
113+
element
114+
}
115+
try {
116+
val (formatted, superfluousParams) = if (formatting) {
117+
format(method, translation, call) ?: (translation to -1)
118+
} else {
119+
(translation to -1)
120+
}
121+
return TranslationInstance(
122+
foldingElement,
123+
index,
124+
referenceElement,
125+
TranslationInstance.Key(prefix, translationKey, suffix),
126+
formatted,
127+
required,
128+
if (superfluousParams >= 0) FormattingError.SUPERFLUOUS else null,
129+
superfluousParams,
130+
)
131+
} catch (ignored: MissingFormatArgumentException) {
132+
return TranslationInstance(
133+
foldingElement,
134+
index,
135+
referenceElement,
136+
TranslationInstance.Key(prefix, translationKey, suffix),
137+
translation,
138+
required,
139+
FormattingError.MISSING,
140+
)
141+
}
142+
}
143+
144+
private fun format(method: PsiMethod, translation: String, call: PsiCall): Pair<String, Int>? {
145+
val format = NUMBER_FORMATTING_PATTERN.replace(translation, "%$1s")
146+
val paramCount = STRING_FORMATTING_PATTERN.findAll(format).count()
147+
148+
val varargs = call.extractVarArgs(method.parameterList.parametersCount - 1, true, true)
149+
val varargStart = if (varargs.size > paramCount) {
150+
method.parameterList.parametersCount - 1 + paramCount
151+
} else {
152+
-1
153+
}
154+
return try {
155+
String.format(format, *varargs) to varargStart
156+
} catch (e: MissingFormatArgumentException) {
157+
// rethrow this specific exception to be handled by the caller
158+
throw e
159+
} catch (e: IllegalFormatException) {
160+
null
161+
}
103162
}
163+
164+
private val NUMBER_FORMATTING_PATTERN = Regex("%(\\d+\\$)?[\\d.]*[df]")
165+
private val STRING_FORMATTING_PATTERN = Regex("[^%]?%(?:\\d+\\$)?s")
104166
}
105167
}

0 commit comments

Comments
 (0)