mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 10:18:26 +07:00
Make tr() a little faster (#2538)
* Make tr() a little faster * Make tr() a little faster - more consistent whitespace * Make tr() a little faster - fix translation file generator * Make tr() a little faster - adapted unit tests
This commit is contained in:
@ -4,11 +4,10 @@ import java.util.HashMap
|
|||||||
|
|
||||||
class TranslationEntry(val entry: String) : HashMap<String, String>() {
|
class TranslationEntry(val entry: String) : HashMap<String, String>() {
|
||||||
|
|
||||||
/** For memory performance on .tr(), which was atrociously memory-expensive */
|
// Now stored in the key of the hashmap storing the instances of this class
|
||||||
var entryWithShortenedSquareBrackets =""
|
// /** For memory performance on .tr(), which was atrociously memory-expensive */
|
||||||
|
// val entryWithShortenedSquareBrackets =
|
||||||
init {
|
// if (entry.contains('['))
|
||||||
if(entry.contains('['))
|
// entry.replace(squareBraceRegex,"[]")
|
||||||
entryWithShortenedSquareBrackets=entry.replace(squareBraceRegex,"[]")
|
// else ""
|
||||||
}
|
|
||||||
}
|
}
|
@ -76,14 +76,22 @@ object TranslationFileWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val translationKey = line.split(" = ")[0].replace("\\n", "\n")
|
val translationKey = line.split(" = ")[0].replace("\\n", "\n")
|
||||||
|
val hashMapKey = if (translationKey.contains('['))
|
||||||
|
translationKey.replace(squareBraceRegex,"[]")
|
||||||
|
else translationKey
|
||||||
var translationValue = ""
|
var translationValue = ""
|
||||||
|
|
||||||
val translationEntry = translations[translationKey]
|
val translationEntry = translations[hashMapKey]
|
||||||
if (translationEntry != null && translationEntry.containsKey(language)) {
|
if (translationEntry != null && translationEntry.containsKey(language)) {
|
||||||
translationValue = translationEntry[language]!!
|
translationValue = translationEntry[language]!!
|
||||||
translationsOfThisLanguage++
|
translationsOfThisLanguage++
|
||||||
} else stringBuilder.appendln(" # Requires translation!")
|
} else stringBuilder.appendln(" # Requires translation!")
|
||||||
|
|
||||||
|
// Testing assertion only
|
||||||
|
if (translationEntry != null && translationEntry.entry != translationKey) {
|
||||||
|
println("Assertion failure in generateTranslationFiles: translationEntry.entry != translationKey")
|
||||||
|
}
|
||||||
|
|
||||||
val lineToWrite = translationKey.replace("\n", "\\n") +
|
val lineToWrite = translationKey.replace("\n", "\\n") +
|
||||||
" = " + translationValue.replace("\n", "\\n")
|
" = " + translationValue.replace("\n", "\\n")
|
||||||
stringBuilder.appendln(lineToWrite)
|
stringBuilder.appendln(lineToWrite)
|
||||||
@ -144,7 +152,10 @@ object TranslationFileWriter {
|
|||||||
val jsonParser = JsonParser()
|
val jsonParser = JsonParser()
|
||||||
val folderHandler = if(modFolder!=null) getFileHandle(modFolder,"jsons")
|
val folderHandler = if(modFolder!=null) getFileHandle(modFolder,"jsons")
|
||||||
else getFileHandle(modFolder, "jsons/Civ V - Vanilla")
|
else getFileHandle(modFolder, "jsons/Civ V - Vanilla")
|
||||||
val listOfJSONFiles = folderHandler.list{file -> file.name.endsWith(".json", true)}
|
val listOfJSONFiles = folderHandler
|
||||||
|
.list{file -> file.name.endsWith(".json", true)}
|
||||||
|
.sortedBy { it.name() } // generatedStrings maintains order, so let's feed it a predictable one
|
||||||
|
|
||||||
for (jsonFile in listOfJSONFiles)
|
for (jsonFile in listOfJSONFiles)
|
||||||
{
|
{
|
||||||
val filename = jsonFile.nameWithoutExtension()
|
val filename = jsonFile.nameWithoutExtension()
|
||||||
|
@ -52,12 +52,17 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (translation in languageTranslations) {
|
for (translation in languageTranslations) {
|
||||||
if (!containsKey(translation.key))
|
// The key for placeholder-containing entries was unused before, let's make it useful
|
||||||
this[translation.key] = TranslationEntry(translation.key)
|
// What was previously stored as entryWithShortenedSquareBrackets is now the key
|
||||||
|
val hashKey = if (translation.key.contains('['))
|
||||||
|
translation.key.replace(squareBraceRegex,"[]")
|
||||||
|
else translation.key
|
||||||
|
if (!containsKey(hashKey))
|
||||||
|
this[hashKey] = TranslationEntry(translation.key)
|
||||||
|
|
||||||
// why not in one line, Because there were actual crashes.
|
// why not in one line, Because there were actual crashes.
|
||||||
// I'm pretty sure I solved this already, but hey double-checking doesn't cost anything.
|
// I'm pretty sure I solved this already, but hey double-checking doesn't cost anything.
|
||||||
val entry = this[translation.key]
|
val entry = this[hashKey]
|
||||||
if (entry!=null) entry[language] = translation.value
|
if (entry!=null) entry[language] = translation.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +142,31 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val squareBraceRegex = Regex("\\[(.*?)\\]") // we don't need to allocate different memory for this every time we .tr()
|
|
||||||
|
|
||||||
val eitherSquareBraceRegex=Regex("\\[|\\]")
|
// we don't need to allocate different memory for these every time we .tr() - or recompile them.
|
||||||
|
|
||||||
|
// Expect a literal [ followed by a captured () group and a literal ].
|
||||||
|
// The group may contain any number of any character except ] - pattern [^]]
|
||||||
|
val squareBraceRegex = Regex("""\[([^]]*)]""")
|
||||||
|
|
||||||
|
// Just look for either [ or ]
|
||||||
|
val eitherSquareBraceRegex = Regex("""\[|]""")
|
||||||
|
|
||||||
|
// Analogous as above: Expect a {} pair with any chars but } in between and capture that
|
||||||
|
val curlyBraceRegex = Regex("""\{([^}]*)}""")
|
||||||
|
|
||||||
|
|
||||||
fun String.tr(): String {
|
fun String.tr(): String {
|
||||||
|
// Latest optimizations: cached precompiled curlyBraceRegex and converted loop-based placeholder
|
||||||
|
// lookup into hash key lookup. Test result: 50s -> 24s
|
||||||
|
// *Discarded optimizations*:
|
||||||
|
// String.indexOf(Char) looks like it might be better than String.contains(String),
|
||||||
|
// (Char test should be cheaper than string comparison, and contains delegates to indexOf anyway)
|
||||||
|
// but measurements are equal within margin of error.
|
||||||
|
// Making our get() overload do two hash lookups instead of up to five: Zero difference.
|
||||||
|
// val entry: TranslationEntry = this[text] ?: return text
|
||||||
|
// return entry[language] ?: text
|
||||||
|
|
||||||
|
|
||||||
// THIS IS INCREDIBLY INEFFICIENT and causes loads of memory problems!
|
// THIS IS INCREDIBLY INEFFICIENT and causes loads of memory problems!
|
||||||
if (contains("[")) { // Placeholders!
|
if (contains("[")) { // Placeholders!
|
||||||
@ -158,10 +183,10 @@ fun String.tr(): String {
|
|||||||
* We will find the german placeholder text, and replace the placeholders with what was filled in the text we got!
|
* We will find the german placeholder text, and replace the placeholders with what was filled in the text we got!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val translationStringWithSquareBracketsOnly = replace(squareBraceRegex,"[]")
|
// Convert "work on [building] has completed in [city]" to "work on [] has completed in []"
|
||||||
|
val translationStringWithSquareBracketsOnly = this.replace(squareBraceRegex,"[]")
|
||||||
val translationEntry = UncivGame.Current.translations.values
|
// That is now the key into the translation HashMap!
|
||||||
.firstOrNull { translationStringWithSquareBracketsOnly == it.entryWithShortenedSquareBrackets }
|
val translationEntry = UncivGame.Current.translations[translationStringWithSquareBracketsOnly]
|
||||||
|
|
||||||
if (translationEntry==null ||
|
if (translationEntry==null ||
|
||||||
!translationEntry.containsKey(UncivGame.Current.settings.language)){
|
!translationEntry.containsKey(UncivGame.Current.settings.language)){
|
||||||
@ -178,11 +203,11 @@ fun String.tr(): String {
|
|||||||
for (i in termsInMessage.indices) {
|
for (i in termsInMessage.indices) {
|
||||||
languageSpecificPlaceholder = languageSpecificPlaceholder.replace(termsInTranslationPlaceholder[i], termsInMessage[i].tr())
|
languageSpecificPlaceholder = languageSpecificPlaceholder.replace(termsInTranslationPlaceholder[i], termsInMessage[i].tr())
|
||||||
}
|
}
|
||||||
return languageSpecificPlaceholder.tr()
|
return languageSpecificPlaceholder // every component is already translated
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contains("{")) { // sentence
|
if (contains("{")) { // sentence
|
||||||
return Regex("\\{(.*?)\\}").replace(this) { it.groups[1]!!.value.tr() }
|
return curlyBraceRegex.replace(this) { it.groups[1]!!.value.tr() }
|
||||||
}
|
}
|
||||||
|
|
||||||
return UncivGame.Current.translations
|
return UncivGame.Current.translations
|
||||||
|
@ -46,11 +46,13 @@ class TranslationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun allStringAreTranslated(strings: Set<String>): Boolean {
|
private fun allStringAreTranslated(strings: Set<String>): Boolean {
|
||||||
|
val squareBraceRegex = Regex("""\[([^]]*)]""")
|
||||||
var allStringsHaveTranslation = true
|
var allStringsHaveTranslation = true
|
||||||
for (key in strings) {
|
for (entry in strings) {
|
||||||
|
val key = if(entry.contains('[')) entry.replace(squareBraceRegex,"[]") else entry
|
||||||
if (!translations.containsKey(key)) {
|
if (!translations.containsKey(key)) {
|
||||||
allStringsHaveTranslation = false
|
allStringsHaveTranslation = false
|
||||||
println(key)
|
println(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allStringsHaveTranslation
|
return allStringsHaveTranslation
|
||||||
@ -72,12 +74,14 @@ class TranslationTests {
|
|||||||
var allTranslationsHaveCorrectPlaceholders = true
|
var allTranslationsHaveCorrectPlaceholders = true
|
||||||
val languages = translations.getLanguages()
|
val languages = translations.getLanguages()
|
||||||
for (key in translations.keys) {
|
for (key in translations.keys) {
|
||||||
val placeholders = placeholderPattern.findAll(key).map { it.value }.toList()
|
val translationEntry = translations[key]!!.entry
|
||||||
|
val placeholders = placeholderPattern.findAll(translationEntry).map { it.value }.toList()
|
||||||
for (language in languages) {
|
for (language in languages) {
|
||||||
for (placeholder in placeholders) {
|
for (placeholder in placeholders) {
|
||||||
if (!translations.get(key, language).contains(placeholder)) {
|
val output = translations.get(translationEntry, language)
|
||||||
|
if (!output.contains(placeholder)) {
|
||||||
allTranslationsHaveCorrectPlaceholders = false
|
allTranslationsHaveCorrectPlaceholders = false
|
||||||
println("Placeholder `$placeholder` not found in `$language` for key `$key`")
|
println("Placeholder `$placeholder` not found in `$language` for entry `$translationEntry`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,6 +92,26 @@ class TranslationTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun allPlaceholderKeysMatchEntry() {
|
||||||
|
val squareBraceRegex = Regex("""\[([^]]*)]""")
|
||||||
|
var allPlaceholderKeysMatchEntry = true
|
||||||
|
for (key in translations.keys) {
|
||||||
|
if ( !key.contains('[') ) continue
|
||||||
|
val translationEntry = translations[key]!!.entry
|
||||||
|
val keyFromEntry = translationEntry.replace(squareBraceRegex,"[]")
|
||||||
|
if (key != keyFromEntry) {
|
||||||
|
allPlaceholderKeysMatchEntry = false
|
||||||
|
println("Entry $translationEntry found under bad key $key")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertTrue(
|
||||||
|
"This test will only pass when all placeholder translations'keys match their entry with shortened placeholders",
|
||||||
|
allPlaceholderKeysMatchEntry
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun allTranslationsEndWithASpace() {
|
fun allTranslationsEndWithASpace() {
|
||||||
val templateLines = Gdx.files.internal(TranslationFileWriter.templateFileLocation).reader().readLines()
|
val templateLines = Gdx.files.internal(TranslationFileWriter.templateFileLocation).reader().readLines()
|
||||||
|
Reference in New Issue
Block a user