Translate numbers to selected language (#11898)

* implement number translation

* fix previous error

* maybe this will fix things?

* try to make the regex more concise and fix the issues

* update implementation

* Transient cache

* implement Number.tr()

* Update core/src/com/unciv/models/metadata/GameSettings.kt

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>

* fix formatting

* move getNumberFormatFromLanguage() to Translations.kt and cache NumberFormat

* remove null cases and resolve suggestions

* why was I doing this? I forgot.

* move getLocaleFromLanguage() to Translations.kt

* add tests

* Update TranslationTests.kt

fix issues

* also remove also

* fix all given cases

* fix missing language param

---------

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
Md. Touhidur Rahman 2024-07-05 19:09:19 +06:00 committed by GitHub
parent ebba2eaeea
commit 461a7ac99d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 9 deletions

View File

@ -7,6 +7,8 @@ import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.multiplayer.FriendList import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.translations.Translations.Companion.getLocaleFromLanguage
import com.unciv.models.translations.Translations.Companion.getNumberFormatFromLanguage
import com.unciv.ui.components.fonts.FontFamilyData import com.unciv.ui.components.fonts.FontFamilyData
import com.unciv.ui.components.fonts.Fonts import com.unciv.ui.components.fonts.Fonts
import com.unciv.ui.components.input.KeyboardBindings import com.unciv.ui.components.input.KeyboardBindings
@ -15,6 +17,7 @@ import com.unciv.ui.screens.worldscreen.NotificationsScroll
import com.unciv.utils.Display import com.unciv.utils.Display
import com.unciv.utils.ScreenOrientation import com.unciv.utils.ScreenOrientation
import java.text.Collator import java.text.Collator
import java.text.NumberFormat
import java.time.Duration import java.time.Duration
import java.util.Locale import java.util.Locale
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -169,14 +172,7 @@ class GameSettings {
} }
fun updateLocaleFromLanguage() { fun updateLocaleFromLanguage() {
val bannedCharacters = listOf(' ', '_', '-', '(', ')') // Things not to have in enum names locale = getLocaleFromLanguage(language)
val languageName = language.filterNot { it in bannedCharacters }
locale = try {
val code = LocaleCode.valueOf(languageName)
Locale(code.language, code.country)
} catch (_: Exception) {
Locale.getDefault()
}
} }
fun getFontSize(): Int { fun getFontSize(): Int {
@ -193,6 +189,10 @@ class GameSettings {
return Collator.getInstance(getCurrentLocale()) return Collator.getInstance(getCurrentLocale())
} }
fun getCurrentNumberFormat(): NumberFormat {
return getNumberFormatFromLanguage(language)
}
//endregion //endregion
//region <Nested classes> //region <Nested classes>

View File

@ -3,6 +3,7 @@ package com.unciv.models.translations
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.metadata.GameSettings.LocaleCode
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
@ -13,6 +14,7 @@ import com.unciv.utils.Log
import com.unciv.utils.debug import com.unciv.utils.debug
import java.util.Locale import java.util.Locale
import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.annotations.VisibleForTesting
import java.text.NumberFormat
/** /**
* This collection holds all translations for the game. * This collection holds all translations for the game.
@ -231,6 +233,26 @@ class Translations : LinkedHashMap<String, TranslationEntry>() {
const val conditionalUniqueOrderString = "ConditionalsPlacement" const val conditionalUniqueOrderString = "ConditionalsPlacement"
const val shouldCapitalizeString = "StartWithCapitalLetter" const val shouldCapitalizeString = "StartWithCapitalLetter"
const val effectBeforeCause = "EffectBeforeCause" const val effectBeforeCause = "EffectBeforeCause"
// NumberFormat cache, key: language, value: NumberFormat
private val languageToNumberFormat = mutableMapOf<String, NumberFormat>()
fun getLocaleFromLanguage(language: String): Locale {
val bannedCharacters =
listOf(' ', '_', '-', '(', ')') // Things not to have in enum names
val languageName = language.filterNot { it in bannedCharacters }
return try {
val code = LocaleCode.valueOf(languageName)
Locale(code.language, code.country)
} catch (_: Exception) {
Locale.getDefault()
}
}
fun getNumberFormatFromLanguage(language: String): NumberFormat =
languageToNumberFormat.getOrPut(language) {
NumberFormat.getInstance(getLocaleFromLanguage(language))
}
} }
} }
@ -252,6 +274,9 @@ val curlyBraceRegex = Regex("""\{([^}]*)\}""")
@Suppress("RegExpRedundantEscape") // Some Android versions need ]}) escaped @Suppress("RegExpRedundantEscape") // Some Android versions need ]}) escaped
val pointyBraceRegex = Regex("""\<([^>]*)\>""") val pointyBraceRegex = Regex("""\<([^>]*)\>""")
// Used to match continous digits 0, 12, 1232 etc
@Suppress("RegExpRedundantEscape") // Some Android versions need ]}) escaped
val digitsRegex = Regex("""\d+""")
object TranslationActiveModsCache { object TranslationActiveModsCache {
private var cachedHash = Int.MIN_VALUE private var cachedHash = Int.MIN_VALUE
@ -443,7 +468,9 @@ private fun String.translatePlaceholders(language: String, hideIcons: Boolean):
private fun String.translateIndividualWord(language: String, hideIcons: Boolean): String { private fun String.translateIndividualWord(language: String, hideIcons: Boolean): String {
if (Stats.isStats(this)) return Stats.parse(this).toString() if (Stats.isStats(this)) return Stats.parse(this).toString()
val translation = UncivGame.Current.translations.getText(this, language, TranslationActiveModsCache.activeMods) val translation = UncivGame.Current.translations.getText(
this, language, TranslationActiveModsCache.activeMods
).replace(digitsRegex) { it.value.toLong().tr(language) }
val stat = Stat.safeValueOf(this) val stat = Stat.safeValueOf(this)
if (stat != null) return stat.character + translation if (stat != null) return stat.character + translation
@ -528,3 +555,13 @@ fun String.removeConditionals(): String {
.replace(" ", " ") .replace(" ", " ")
.trim() .trim()
} }
// formats number according to current language
fun Number.tr(): String {
return UncivGame.Current.settings.getCurrentNumberFormat().format(this)
}
// formats number according to given language
fun Number.tr(language: String): String {
return Translations.getNumberFormatFromLanguage(language).format(this)
}

View File

@ -5,6 +5,7 @@ import com.badlogic.gdx.Gdx
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.GameSettings.LocaleCode
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
@ -392,6 +393,52 @@ class TranslationTests {
additionalTest?.invoke(translated) additionalTest?.invoke(translated)
} }
@Test
fun testNumberTr() {
UncivGame.Current = UncivGame()
UncivGame.Current.settings = GameSettings()
val testCases = arrayOf(1, -1, 0.123, -0.123)
val expectedEnglishOutputs = arrayOf("1", "-1", "0.123", "-0.123")
Assert.assertArrayEquals(
"Number.tr()", expectedEnglishOutputs, testCases.map { it.tr() }.toTypedArray()
)
Assert.assertArrayEquals(
"Number.tr(${LocaleCode.English.name})",
expectedEnglishOutputs,
testCases.map { it.tr(LocaleCode.English.name) }.toTypedArray()
)
val expectedBanglaOutputs = arrayOf("", "-১", ".১২৩", "-.১২৩")
Assert.assertArrayEquals(
"Number.tr(${LocaleCode.Bangla.name})",
expectedBanglaOutputs,
testCases.map { it.tr(LocaleCode.Bangla.name) }.toTypedArray()
)
}
@Test
fun testStringsWithNumbers() {
UncivGame.Current = UncivGame()
UncivGame.Current.settings = GameSettings()
val tests = arrayOf("1", "+1", "-1", "1.0", "+1.0", "-1.0", "0%", "1/2", "(3/4)")
UncivGame.Current.settings.language = LocaleCode.English.name
Assert.assertArrayEquals(
"English", tests, // assume unchanged
tests.map { it.tr() }.toTypedArray()
)
UncivGame.Current.settings.language = LocaleCode.Bangla.name
Assert.assertArrayEquals(
"Bangla",
arrayOf("", "+১", "-১", "১.", "+১.", "-১.", "%", "১/২", "(৩/)"),
tests.map { it.tr() }.toTypedArray()
)
}
// @Test // @Test
// fun allConditionalsAreContainedInConditionalOrderTranslation() { // fun allConditionalsAreContainedInConditionalOrderTranslation() {
// val orderedConditionals = Translations.englishConditionalOrderingString // val orderedConditionals = Translations.englishConditionalOrderingString