From dcaf3bd68edf5a01de6eef8890017e1ae1c22342 Mon Sep 17 00:00:00 2001 From: yairm210 Date: Mon, 1 Jul 2024 09:21:48 +0300 Subject: [PATCH] Split tr() into subfunctions, was getting very large for a single function --- .../unciv/models/translations/Translations.kt | 225 +++++++++--------- 1 file changed, 119 insertions(+), 106 deletions(-) diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index 80c6258863..331d1d8c13 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -310,124 +310,137 @@ fun String.tr(hideIcons: Boolean = false): String { val language: String = UncivGame.Current.settings.language // '<' and '>' checks for quick 'no' answer, regex to ensure that no one accidentally put '><' and ruined things - if (contains('<') && contains('>') && pointyBraceRegex.containsMatchIn(this)) { // Conditionals! - /** - * So conditionals can contain placeholders, such as , which themselves - * can contain multiple filters, such as . - * Moreover, we can have any amount of conditionals in any order, and translations - * can reorder these conditionals in any way they like, even putting them in front - * of the rest of the translatable string. - * All of this nesting makes it quite difficult to translate, and is the reason we check - * for these first. - * - * The plan: First translate each of the conditionals on its own, and then combine them - * together into the final fully translated string. - */ - - var translatedBaseUnique = this.removeConditionals().tr(hideIcons) - - val conditionals = this.getConditionals().map { it.placeholderText } - val conditionsWithTranslation: LinkedHashMap = linkedMapOf() - - for (conditional in this.getConditionals()) - conditionsWithTranslation[conditional.placeholderText] = conditional.text.tr(hideIcons) - - val translatedConditionals: MutableList = mutableListOf() - - // Somewhere, we asked the translators to reorder all possible conditionals in a way that - // makes sense in their language. We get this ordering, and than extract each of the - // translated conditionals, removing the <> surrounding them, and removing param values - // where it exists. - val conditionalOrdering = UncivGame.Current.translations.getConditionalOrder(language) - for (placedConditional in pointyBraceRegex.findAll(conditionalOrdering) - .map { it.value.substring(1, it.value.length - 1).getPlaceholderText() }) { - if (placedConditional in conditionals) { - translatedConditionals.add(conditionsWithTranslation[placedConditional]!!) - conditionsWithTranslation.remove(placedConditional) - } - } - - // If the translated string that should contain all conditionals doesn't contain - // a few conditionals used here, just add the translations of these to the end. - // We do test for this, but just in case. - translatedConditionals.addAll(conditionsWithTranslation.values) - - // After that, add the translation of the base unique either before or after these conditionals - if (UncivGame.Current.translations.placeConditionalsAfterUnique(language)) { - translatedConditionals.add(0, translatedBaseUnique) - } else { - if (UncivGame.Current.translations.shouldCapitalize(language) && translatedBaseUnique[0].isUpperCase()) - translatedBaseUnique = translatedBaseUnique.replaceFirstChar { it.lowercase() } - translatedConditionals.add(translatedBaseUnique) - } - - var fullyTranslatedString = translatedConditionals.joinToString( - UncivGame.Current.translations.getSpaceEquivalent(language) - ) - if (UncivGame.Current.translations.shouldCapitalize(language)) - fullyTranslatedString = fullyTranslatedString.replaceFirstChar { it.uppercase() } - return fullyTranslatedString + if (contains('<') && contains('>') && pointyBraceRegex.containsMatchIn(this)) { + return translateConditionals(hideIcons, language) } // curly and square brackets can be nested inside of each other so find the leftmost curly/square // bracket then process that first val indexSquare = this.indexOf('[') val indexCurly = this.indexOf('{') - val processSquare = indexSquare >= 0 && (indexCurly < 0 || indexSquare < indexCurly) - val processCurly = indexCurly >= 0 && (indexSquare < 0 || indexCurly < indexSquare) - // There might still be optimization potential here! - if (processSquare) { // Placeholders! - /** - * I'm SURE there's an easier way to do this but I can't think of it =\ - * So what's all this then? - * Well, not all languages are like English. So say I want to say "work on Library has completed in Akkad", - * but in a completely different language like Japanese or German, - * It could come out "Akkad hast die worken onner Library gerfinishen" or whatever, - * basically, the order of the words in the sentence is not guaranteed. - * So to translate this, I give a sentence like "work on [building] has completed in [city]" - * and the german can put those placeholders where he wants, so "[city] hast die worken onner [building] gerfinishen" - * The string on which we call tr() will look like "work on [library] has completed in [Akkad]" - * We will find the german placeholder text, and replace the placeholders with what was filled in the text we got! - */ + val squareBracketsEncounteredFirst = indexSquare >= 0 && (indexCurly < 0 || indexSquare < indexCurly) + val curlyBracketsEncounteredFirst = indexCurly >= 0 && (indexSquare < 0 || indexCurly < indexSquare) - // Convert "work on [building] has completed in [city]" to "work on [] has completed in []" - val translationStringWithSquareBracketsOnly = this.getPlaceholderText() - // That is now the key into the translation HashMap! - val translationEntry = UncivGame.Current.translations - .get(translationStringWithSquareBracketsOnly, language, TranslationActiveModsCache.activeMods) + if (squareBracketsEncounteredFirst) + return translatePlaceholders(language, hideIcons) - var languageSpecificPlaceholder: String - val originalEntry: String - if (translationEntry == null || !translationEntry.containsKey(language)) { - // Translation placeholder doesn't exist for this language, default to English - languageSpecificPlaceholder = this - originalEntry = this - } else { - languageSpecificPlaceholder = translationEntry[language]!! - originalEntry = translationEntry.entry - } - - // Take the terms in the message, WITHOUT square brackets - val termsInMessage = this.getPlaceholderParameters() - // Take the terms from the placeholder - val termsInTranslationPlaceholder = originalEntry.getPlaceholderParameters() - if (termsInMessage.size != termsInTranslationPlaceholder.size) - throw Exception("Message $this has a different number of terms than the placeholder $translationEntry!") - - for (i in termsInMessage.indices) { - languageSpecificPlaceholder = languageSpecificPlaceholder.replace( - "[${termsInTranslationPlaceholder[i]}]", // re-add square brackets to placeholder terms - termsInMessage[i].tr(hideIcons) - ) - } - return languageSpecificPlaceholder // every component is already translated - } - - if (processCurly) { // Translating partial sentences + if (curlyBracketsEncounteredFirst) // Translating partial sentences return curlyBraceRegex.replace(this) { it.groups[1]!!.value.tr(hideIcons) } + + return translateIndividualWord(language, hideIcons) +} + + +private fun String.translateConditionals(hideIcons: Boolean, language: String): String { + /** + * So conditionals can contain placeholders, such as , which themselves + * can contain multiple filters, such as . + * Moreover, we can have any amount of conditionals in any order, and translations + * can reorder these conditionals in any way they like, even putting them in front + * of the rest of the translatable string. + * All of this nesting makes it quite difficult to translate, and is the reason we check + * for these first. + * + * The plan: First translate each of the conditionals on its own, and then combine them + * together into the final fully translated string. + */ + + var translatedBaseUnique = this.removeConditionals().tr(hideIcons) + + val conditionals = this.getConditionals().map { it.placeholderText } + val conditionsWithTranslation: LinkedHashMap = linkedMapOf() + + for (conditional in this.getConditionals()) + conditionsWithTranslation[conditional.placeholderText] = conditional.text.tr(hideIcons) + + val translatedConditionals: MutableList = mutableListOf() + + // Somewhere, we asked the translators to reorder all possible conditionals in a way that + // makes sense in their language. We get this ordering, and than extract each of the + // translated conditionals, removing the <> surrounding them, and removing param values + // where it exists. + val conditionalOrdering = UncivGame.Current.translations.getConditionalOrder(language) + for (placedConditional in pointyBraceRegex.findAll(conditionalOrdering) + .map { it.value.substring(1, it.value.length - 1).getPlaceholderText() }) { + if (placedConditional in conditionals) { + translatedConditionals.add(conditionsWithTranslation[placedConditional]!!) + conditionsWithTranslation.remove(placedConditional) + } } + // If the translated string that should contain all conditionals doesn't contain + // a few conditionals used here, just add the translations of these to the end. + // We do test for this, but just in case. + translatedConditionals.addAll(conditionsWithTranslation.values) + + // After that, add the translation of the base unique either before or after these conditionals + if (UncivGame.Current.translations.placeConditionalsAfterUnique(language)) { + translatedConditionals.add(0, translatedBaseUnique) + } else { + if (UncivGame.Current.translations.shouldCapitalize(language) && translatedBaseUnique[0].isUpperCase()) + translatedBaseUnique = translatedBaseUnique.replaceFirstChar { it.lowercase() } + translatedConditionals.add(translatedBaseUnique) + } + + var fullyTranslatedString = translatedConditionals.joinToString( + UncivGame.Current.translations.getSpaceEquivalent(language) + ) + if (UncivGame.Current.translations.shouldCapitalize(language)) + fullyTranslatedString = fullyTranslatedString.replaceFirstChar { it.uppercase() } + return fullyTranslatedString +} + +private fun String.translatePlaceholders(language: String, hideIcons: Boolean): String { + /** + * I'm SURE there's an easier way to do this but I can't think of it =\ + * So what's all this then? + * Well, not all languages are like English. So say I want to say "work on Library has completed in Akkad", + * but in a completely different language like Japanese or German, + * It could come out "Akkad hast die worken onner Library gerfinishen" or whatever, + * basically, the order of the words in the sentence is not guaranteed. + * So to translate this, I give a sentence like "work on [building] has completed in [city]" + * and the german can put those placeholders where he wants, so "[city] hast die worken onner [building] gerfinishen" + * The string on which we call tr() will look like "work on [library] has completed in [Akkad]" + * We will find the german placeholder text, and replace the placeholders with what was filled in the text we got! + */ + + // Convert "work on [building] has completed in [city]" to "work on [] has completed in []" + val translationStringWithSquareBracketsOnly = this.getPlaceholderText() + // That is now the key into the translation HashMap! + val translationEntry = UncivGame.Current.translations + .get(translationStringWithSquareBracketsOnly, language, TranslationActiveModsCache.activeMods) + + var languageSpecificPlaceholder: String + val originalEntry: String + if (translationEntry == null || !translationEntry.containsKey(language)) { + // Translation placeholder doesn't exist for this language, default to English + languageSpecificPlaceholder = this + originalEntry = this + } else { + languageSpecificPlaceholder = translationEntry[language]!! + originalEntry = translationEntry.entry + } + + // Take the terms in the message, WITHOUT square brackets + val termsInMessage = this.getPlaceholderParameters() + // Take the terms from the placeholder + val termsInTranslationPlaceholder = originalEntry.getPlaceholderParameters() + if (termsInMessage.size != termsInTranslationPlaceholder.size) + throw Exception("Message $this has a different number of terms than the placeholder $translationEntry!") + + for (i in termsInMessage.indices) { + languageSpecificPlaceholder = languageSpecificPlaceholder.replace( + "[${termsInTranslationPlaceholder[i]}]", // re-add square brackets to placeholder terms + termsInMessage[i].tr(hideIcons) + ) + } + return languageSpecificPlaceholder // every component is already translated +} + + +/** No brackets of any kind, just a single word */ +private fun String.translateIndividualWord(language: String, hideIcons: Boolean): String { if (Stats.isStats(this)) return Stats.parse(this).toString() val translation = UncivGame.Current.translations.getText(this, language, TranslationActiveModsCache.activeMods)