diff --git a/core/src/com/unciv/models/ruleset/Belief.kt b/core/src/com/unciv/models/ruleset/Belief.kt index a349646daec2d..a214dfa905fdd 100644 --- a/core/src/com/unciv/models/ruleset/Belief.kt +++ b/core/src/com/unciv/models/ruleset/Belief.kt @@ -2,16 +2,17 @@ package com.unciv.models.ruleset import com.unciv.UncivGame import com.unciv.models.stats.INamed -import com.unciv.ui.civilopedia.CivilopediaText import com.unciv.ui.civilopedia.FormattedLine +import com.unciv.ui.civilopedia.ICivilopediaText import java.util.ArrayList -class Belief: INamed, CivilopediaText() { +class Belief : INamed, ICivilopediaText { override var name: String = "" var type: BeliefType = BeliefType.None var uniques = ArrayList() val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + override var civilopediaText = listOf() override fun makeLink() = "Belief/$name" override fun replacesCivilopediaDescription() = true diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 927d00e6ad606..e9e83dfb93881 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -9,7 +9,7 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Unique import com.unciv.models.stats.INamed import com.unciv.models.translations.tr -import com.unciv.ui.civilopedia.CivilopediaText +import com.unciv.ui.civilopedia.ICivilopediaText import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts import kotlin.math.pow @@ -18,7 +18,7 @@ import kotlin.math.pow /** This is the basic info of the units, as specified in Units.json, in contrast to MapUnit, which is a specific unit of a certain type that appears on the map */ -class BaseUnit : INamed, IConstruction, CivilopediaText() { +class BaseUnit : INamed, IConstruction, ICivilopediaText { override lateinit var name: String var cost: Int = 0 @@ -41,6 +41,8 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() { var uniqueTo: String? = null var attackSound: String? = null + override var civilopediaText = listOf() + fun getShortDescription(): String { val infoList = mutableListOf() if (strength != 0) infoList += "$strength${Fonts.strength}" diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index acc0c07e55ef7..473a91e1e9ea1 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -16,6 +16,7 @@ import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import java.lang.reflect.Field +import java.lang.reflect.Modifier object TranslationFileWriter { @@ -181,6 +182,52 @@ object TranslationFileWriter { } private fun generateStringsFromJSONs(jsonsFolder: FileHandle): LinkedHashMap> { + // build maps identifying parameters as certain types of filters - unitFilter etc + val ruleset = RulesetCache.getBaseRuleset() + val tileFilterMap = ruleset.terrains.keys.toMutableSet().apply { addAll(sequenceOf( + "Friendly Land", + "Foreign Land", + "Fresh water", + "non-fresh water", + "Open Terrain", + "Rough Terrain", + "Natural Wonder" + )) } + val tileImprovementMap = ruleset.tileImprovements.keys.toMutableSet().apply { add("Great Improvement") } + val buildingMap = ruleset.buildings.keys.toMutableSet().apply { addAll(sequenceOf( + "Wonders", + "Wonder", + "Buildings", + "Building" + )) } + val unitTypeMap = UnitType.values().map { it.name }.toMutableSet().apply { addAll(sequenceOf( + "Military", + "Civilian", + "non-air", + "relevant", + "Nuclear Weapon", + "Submarine", + // These are up for debate + "Air", + "land units", + "water units", + "air units", + "military units", + "submarine units" + // Note: this can't handle combinations of parameters (e.g. [{Military} {Water}]) + )) } + val cityFilterMap = setOf( + "in this city", + "in all cities", + "in all coastal cities", + "in capital", + "in all non-occupied cities", + "in all cities with a world wonder", + "in all cities connected to capital", + "in all cities with a garrison" + ) + + val startMillis = System.currentTimeMillis() // Using LinkedHashMap (instead of HashMap) is important to maintain the order of sections in the translation file val generatedStrings = LinkedHashMap>() @@ -215,55 +262,16 @@ object TranslationFileWriter { var parameterName = when { parameter.toFloatOrNull() != null -> "amount" Stat.values().any { it.name == parameter } -> "stat" - RulesetCache.getBaseRuleset().terrains.containsKey(parameter) - || parameter == "Friendly Land" - || parameter == "Foreign Land" - || parameter == "Fresh water" - || parameter == "non-fresh water" - || parameter == "Open Terrain" - || parameter == "Rough Terrain" - || parameter == "Natural Wonder" - -> "tileFilter" - RulesetCache.getBaseRuleset().units.containsKey(parameter) -> "unit" - RulesetCache.getBaseRuleset().tileImprovements.containsKey(parameter) - || parameter == "Great Improvement" - -> "tileImprovement" - RulesetCache.getBaseRuleset().tileResources.containsKey(parameter) -> "resource" - RulesetCache.getBaseRuleset().technologies.containsKey(parameter) -> "tech" - RulesetCache.getBaseRuleset().unitPromotions.containsKey(parameter) -> "promotion" - RulesetCache.getBaseRuleset().buildings.containsKey(parameter) - || parameter == "Wonders" - || parameter == "Wonder" - || parameter == "Buildings" - || parameter == "Building" - -> "building" - - UnitType.values().any { it.name == parameter } - || parameter == "Military" - || parameter == "Civilian" - || parameter == "non-air" - || parameter == "relevant" - || parameter == "Nuclear Weapon" - || parameter == "Submarine" - // These are up for debate - || parameter == "Air" - || parameter == "land units" - || parameter == "water units" - || parameter == "air units" - || parameter == "military units" - || parameter == "submarine units" - // Note: this can't handle combinations of parameters (e.g. [{Military} {Water}]) - -> "unitType" + parameter in tileFilterMap -> "tileFilter" + ruleset.units.containsKey(parameter) -> "unit" + parameter in tileImprovementMap -> "tileImprovement" + ruleset.tileResources.containsKey(parameter) -> "resource" + ruleset.technologies.containsKey(parameter) -> "tech" + ruleset.unitPromotions.containsKey(parameter) -> "promotion" + parameter in buildingMap -> "building" + parameter in unitTypeMap -> "unitType" Stats.isStats(parameter) -> "stats" - parameter == "in this city" - || parameter == "in all cities" - || parameter == "in all coastal cities" - || parameter == "in capital" - || parameter == "in all non-occupied cities" - || parameter == "in all cities with a world wonder" - || parameter == "in all cities connected to capital" - || parameter == "in all cities with a garrison" - -> "cityFilter" + parameter in cityFilterMap -> "cityFilter" else -> "param" } if (parameterName in existingParameterNames) { @@ -285,23 +293,34 @@ object TranslationFileWriter { submitString(element) return } - val allFields = (element.javaClass.declaredFields + element.javaClass.fields - + element.javaClass.superclass.declaredFields) // This is so the main PolicyBranch, which inherits from Policy, will recognize its Uniques and have them translated - .filter { + val allFields = ( + element.javaClass.declaredFields + + element.javaClass.fields + // Include superclass so the main PolicyBranch, which inherits from Policy, + // will recognize its Uniques and have them translated + + element.javaClass.superclass.declaredFields + ).filter { it.type == String::class.java || - it.type == java.util.ArrayList::class.java || - it.type == java.util.HashSet::class.java + it.type == java.util.ArrayList::class.java || + it.type == java.util.List::class.java || // CivilopediaText is not an ArrayList + it.type == java.util.HashSet::class.java || + it.type.isEnum // allow scanning Enum names } for (field in allFields) { field.isAccessible = true val fieldValue = field.get(element) if (isFieldTranslatable(field, fieldValue)) { // skip fields which must not be translated // this field can contain sub-objects, let's serialize them as well - if (fieldValue is java.util.AbstractCollection<*>) { - for (item in fieldValue) - if (item is String) submitString(item) else serializeElement(item!!) - } else - submitString(fieldValue) + @Suppress("RemoveRedundantQualifierName") // to clarify List does _not_ inherit from anything in java.util + when (fieldValue) { + is java.util.AbstractCollection<*> -> + for (item in fieldValue) + if (item is String) submitString(item) else serializeElement(item!!) + is kotlin.collections.List<*> -> + for (item in fieldValue) + if (item is String) submitString(item) else serializeElement(item!!) + else -> submitString(fieldValue) + } } } } @@ -313,6 +332,8 @@ object TranslationFileWriter { resultStrings!!.add("$specialNewLineCode ${uniqueIndexOfNewLine++}") } } + println("Translation writer took ${System.currentTimeMillis()-startMillis}ms for ${jsonsFolder.name()}") + return generatedStrings } @@ -323,16 +344,21 @@ object TranslationFileWriter { "providesFreeBuilding", "replaces", "requiredBuilding", "requiredBuildingInAllCities", "requiredNearbyImprovedResources", "requiredResource", "requiredTech", "requires", "resourceTerrainAllow", "revealedBy", "startBias", "techRequired", - "terrainsCanBeBuiltOn", "terrainsCanBeFoundOn", "turnsInto", "uniqueTo", "upgradesTo" + "terrainsCanBeBuiltOn", "terrainsCanBeFoundOn", "turnsInto", "uniqueTo", "upgradesTo", + "link", "icon", "extraImage", "color" // FormattedLine ) + private val translatableEnumsSet = setOf("BeliefType") private fun isFieldTranslatable(field: Field, fieldValue: Any?): Boolean { // Exclude fields by name that contain references to items defined elsewhere - // - the definition should cause the inclusion in our translatables list, not the reference. - // This prevents duplication within the base game (e.g. Mines were duplicated by being output - // by both TerrainResources and TerrainImprovements) and duplication of base game items into + // or are otherwise Strings but not user-displayed. + // The Modifier.STATIC exclusion removes fields from e.g. companion objects. + // Fields of enum types need that type explicitly allowed in translatableEnumsSet + // (Using an annotation failed, even with @Retention RUNTIME they were invisible to reflection) return fieldValue != null && fieldValue != "" && + (field.modifiers and Modifier.STATIC) == 0 && + (!field.type.isEnum || field.type.simpleName in translatableEnumsSet) && field.name !in untranslatableFieldSet } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index 7ec9dfa8ce6a8..e5a45622e1b19 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -2,6 +2,7 @@ package com.unciv.ui.worldscreen.mainmenu import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.* import com.unciv.MainMenuScreen @@ -13,6 +14,7 @@ import com.unciv.models.translations.TranslationFileWriter import com.unciv.models.translations.Translations import com.unciv.models.translations.tr import com.unciv.ui.utils.* +import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread @@ -245,7 +247,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr private fun addTranslationGeneration() { if (Gdx.app.type == Application.ApplicationType.Desktop) { val generateTranslationsButton = "Generate translation files".toTextButton() - generateTranslationsButton.onClick { + val generateAction = { val translations = Translations() translations.readAllLanguagesTranslation() TranslationFileWriter.writeNewTranslationFiles(translations) @@ -253,6 +255,9 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr generateTranslationsButton.setText("Translation files are generated successfully.".tr()) generateTranslationsButton.disable() } + generateTranslationsButton.onClick(generateAction) + keyPressDispatcher[Input.Keys.F12] = generateAction + generateTranslationsButton.addTooltip("F12",18f) optionsTable.add(generateTranslationsButton).colspan(2).row() } }