diff --git a/core/src/com/unciv/logic/files/FileChooser.kt b/core/src/com/unciv/logic/files/FileChooser.kt index 85e6a81128..47ba9e23c1 100644 --- a/core/src/com/unciv/logic/files/FileChooser.kt +++ b/core/src/com/unciv/logic/files/FileChooser.kt @@ -143,7 +143,7 @@ open class FileChooser( add(fileScroll).colspan(2).fill().row() addSeparator(height = 1f) fileNameCell = add().colspan(2).growX() - row() + super.row() addCloseButton(Constants.cancel) { reportResult(false) diff --git a/core/src/com/unciv/logic/files/UncivFiles.kt b/core/src/com/unciv/logic/files/UncivFiles.kt index 76b3e013ad..04cdeeaac1 100644 --- a/core/src/com/unciv/logic/files/UncivFiles.kt +++ b/core/src/com/unciv/logic/files/UncivFiles.kt @@ -59,7 +59,7 @@ class UncivFiles( } fun getModsFolder() = getLocalFile("mods") - fun getModFolder(modName: String) = getModsFolder().child(modName) + fun getModFolder(modName: String): FileHandle = getModsFolder().child(modName) /** The folder that holds data that the game changes while running - all the mods, maps, save files, etc */ fun getDataFolder() = getLocalFile("") @@ -167,7 +167,7 @@ class UncivFiles( /** * Only use this with a [FileHandle] obtained by one of the methods of this class! */ - fun saveGame(game: GameInfo, file: FileHandle, saveCompletionCallback: (Exception?) -> Unit = { if (it != null) throw it }) { + private fun saveGame(game: GameInfo, file: FileHandle, saveCompletionCallback: (Exception?) -> Unit = { if (it != null) throw it }) { try { debug("Saving GameInfo %s to %s", game.gameId, file.path()) val string = gameInfoToString(game) @@ -322,7 +322,7 @@ class UncivFiles( //endregion //region Scenarios - val scenarioFolder = "scenarios" + private val scenarioFolder = "scenarios" fun getScenarioFiles() = sequence { for (mod in RulesetCache.values) { val modFolder = mod.folderLocation ?: continue diff --git a/core/src/com/unciv/logic/github/Github.kt b/core/src/com/unciv/logic/github/Github.kt index 924bb6d618..a579458d3e 100644 --- a/core/src/com/unciv/logic/github/Github.kt +++ b/core/src/com/unciv/logic/github/Github.kt @@ -19,7 +19,7 @@ import java.io.FileFilter import java.io.InputStream import java.io.InputStreamReader import java.net.HttpURLConnection -import java.net.URL +import java.net.URI import java.nio.ByteBuffer import java.util.zip.ZipException @@ -48,7 +48,7 @@ object Github { // Consider merging this with the Dropbox function /** * Helper opens an url and accesses its input stream, logging errors to the console - * @param url String representing a [URL] to download. + * @param url String representing a [URI] to download. * @param preDownloadAction Optional callback that will be executed between opening the connection and * accessing its data - passes the [connection][HttpURLConnection] and allows e.g. reading the response headers. * @return The [InputStream] if successful, `null` otherwise. @@ -56,9 +56,8 @@ object Github { fun download(url: String, preDownloadAction: (HttpURLConnection) -> Unit = {}): InputStream? { try { // Problem type 1 - opening the URL connection - @Suppress("DEPRECATION") // We still support Java < 20 - with(URL(url).openConnection() as HttpURLConnection) - { + // URL(string) is deprecated, URI.toUrl(string) API level 36, see [Android Doc](https://developer.android.com/reference/java/net/URI#toURL()): + with(URI(url).toURL().openConnection() as HttpURLConnection) { preDownloadAction(this) // Problem type 2 - getting the information try { diff --git a/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt b/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt index e5ea0ac4a2..117311b82c 100644 --- a/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt +++ b/core/src/com/unciv/logic/multiplayer/storage/DropBox.kt @@ -9,8 +9,7 @@ import java.io.DataOutputStream import java.io.InputStream import java.io.InputStreamReader import java.net.HttpURLConnection -import java.net.URL -import java.nio.charset.Charset +import java.net.URI import java.util.Date import java.util.Timer import kotlin.concurrent.timer @@ -25,7 +24,8 @@ object DropBox: FileStorage { if (remainingRateLimitSeconds > 0) throw FileStorageRateLimitReached(remainingRateLimitSeconds) - with(URL(url).openConnection() as HttpURLConnection) { + // URL(string) is deprecated, URI.toUrl(string) API level 36: + with(URI(url).toURL().openConnection() as HttpURLConnection) { requestMethod = "POST" // default is GET @Suppress("SpellCheckingInspection") diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 88af8c84e7..59a2779820 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -7,8 +7,6 @@ import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.multiplayer.FriendList 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.Fonts import com.unciv.ui.components.input.KeyboardBindings @@ -175,7 +173,7 @@ class GameSettings { } fun updateLocaleFromLanguage() { - locale = getLocaleFromLanguage(language) + locale = LocaleCode.getLocale(language) } fun getFontSize(): Int { @@ -193,7 +191,7 @@ class GameSettings { } fun getCurrentNumberFormat(): NumberFormat { - return getNumberFormatFromLanguage(language) + return LocaleCode.getNumberFormatFromLanguage(language) } //endregion @@ -262,67 +260,6 @@ class GameSettings { enum class NationPickerListMode { Icons, List } - /** Map Unciv language key to Java locale, for the purpose of getting a Collator for sorting. - * - Effect depends on the Java libraries and may not always conform to expectations. - * If in doubt, debug and see what Locale instance you get and compare its properties with `Locale.getDefault()`. - * (`Collator.getInstance(LocaleCode.*.run { Locale(language, country) }) to Collator.getInstance()`, drill to both `rules`, compare hashes - if equal and other properties equal, then Java doesn't know your Language)) - * @property name same as translation file name with ' ', '_', '-', '(', ')' removed - * @property language ISO 639-1 code for the language - * @property country ISO 3166 code for the nation this is predominantly spoken in - * @property trueLanguage If set, used instead of language to trick Java into supplying a close-enough collator (a no-match would otherwise give us the default collator, not a collator for a partial match) - */ - enum class LocaleCode(val language: String, val country: String, val trueLanguage: String? = null) { - Afrikaans("af", "ZA"), - Arabic("ar", "IQ"), - Bangla("bn", "BD"), - Belarusian("be", "BY"), - Bosnian("bs", "BA"), - BrazilianPortuguese("pt", "BR"), - Bulgarian("bg", "BG"), - Catalan("ca", "ES"), - Croatian("hr", "HR"), - Czech("cs", "CZ"), - Danish("da", "DK"), - Dutch("nl", "NL"), - English("en", "US"), - Estonian("et", "EE"), - Finnish("fi", "FI"), - French("fr", "FR"), - Galician("gl", "ES"), - German("de", "DE"), - Greek("el", "GR"), - Hindi("hi", "IN"), - Hungarian("hu", "HU"), - Indonesian("in", "ID"), - Italian("it", "IT"), - Japanese("ja", "JP"), - Korean("ko", "KR"), - Latin("la", "IT"), - Latvian("lv", "LV"), - Lithuanian("lt", "LT"), - Malay("ms", "MY"), - Norwegian("no", "NO"), - NorwegianNynorsk("nn", "NO"), - PersianPinglishDIN("fa", "IR"), // These might just fall back to default - PersianPinglishUN("fa", "IR"), - Polish("pl", "PL"), - Portuguese("pt", "PT"), - Romanian("ro", "RO"), - Russian("ru", "RU"), - Rusyn("uk", "UA", "rus"), // No specific locale for rus exists, so use closest for collator - Serbian("sr", "RS"), - SimplifiedChinese("zh", "CN"), - Slovak("sk", "SK"), - Spanish("es", "ES"), - Swedish("sv", "SE"), - Thai("th", "TH"), - TraditionalChinese("zh", "TW"), - Turkish("tr", "TR"), - Ukrainian("uk", "UA"), - Vietnamese("vi", "VN"), - Zulu("zu", "ZA") - } - //endregion //region Multiplayer-specific diff --git a/core/src/com/unciv/models/metadata/LocaleCode.kt b/core/src/com/unciv/models/metadata/LocaleCode.kt new file mode 100644 index 0000000000..d699c45e52 --- /dev/null +++ b/core/src/com/unciv/models/metadata/LocaleCode.kt @@ -0,0 +1,97 @@ +package com.unciv.models.metadata + +import java.text.NumberFormat +import java.util.Locale + +/** Map Unciv language key to Java locale, for the purpose of getting a Collator for sorting. + * - Effect depends on the Java libraries and may not always conform to expectations. + * If in doubt, debug and see what Locale instance you get and compare its properties with `Locale.getDefault()`. + * (`Collator.getInstance(LocaleCode.*.run { Locale(language, country) }) to Collator.getInstance()`, drill to both `rules`, compare hashes - if equal and other properties equal, then Java doesn't know your Language)) + * - For languages without an easy predefined Locale, collation or numeric formats can be forced using [Unicode Extensions for BCP 47](https://www.unicode.org/reports/tr35/#Locale_Extension_Key_and_Type_Data). + * + * @property name **Must** be the same as the translation file name with ' ', '_', '-', '(', ')' removed + * @property languageTag IETF BCP 47 language tag - see [forLanguageTag][Locale.forLanguageTag] or [Android reference][https://developer.android.com/reference/java/util/Locale#forLanguageTag(java.lang.String)] + * Usually the ISO 639-1 code for the language, a dash, and the ISO 3166 code for the nation this is predominantly spoken in + * @property fastlaneFolder If set, it's used instead of the language part of [languageTag] as fastlane folder name + */ +enum class LocaleCode(val languageTag: String, private val fastlaneFolder: String? = null) { + Afrikaans("af-ZA"), + Arabic("ar-IQ"), + Bangla("bn-BD"), + Belarusian("be-BY"), + Bosnian("bs-BA"), + BrazilianPortuguese("pt-BR"), + Bulgarian("bg-BG"), + Catalan("ca-ES"), + Croatian("hr-HR"), + Czech("cs-CZ"), + Danish("da-DK"), + Dutch("nl-NL"), + English("en-US"), + Estonian("et-EE"), + Finnish("fi-FI"), + French("fr-FR"), + Galician("gl-ES"), + German("de-DE"), + Greek("el-GR"), + Hindi("hi-IN"), + Hungarian("hu-HU"), + Indonesian("in-ID"), + Italian("it-IT"), + Japanese("ja-JP"), + Korean("ko-KR"), + Latin("la-IT"), + Latvian("lv-LV"), + Lithuanian("lt-LT"), + Malay("ms-MY"), + Norwegian("no-NO"), + NorwegianNynorsk("nn-NO"), + PersianPinglishDIN("fa-IR"), // These might just fall back to default + PersianPinglishUN("fa-IR"), + Polish("pl-PL"), + Portuguese("pt-PT"), + Romanian("ro-RO"), + Russian("ru-RU"), + Rusyn("rue-SK-u-kr-cyrl-latn-digit", "rue"), // No specific locale exists, so use explicit cyrillic collation. Chose country with most speakers. + Serbian("sr-RS"), + SimplifiedChinese("zh-CN"), + Slovak("sk-SK"), + Spanish("es-ES"), + Swedish("sv-SE"), + Thai("th-TH"), + TraditionalChinese("zh-TW"), + Turkish("tr-TR"), + Ukrainian("uk-UA"), + Vietnamese("vi-VN"), + Zulu("zu-ZA") + ; + + fun locale(): Locale = Locale.forLanguageTag(languageTag) + fun fastlaneFolder(): String = this.fastlaneFolder ?: locale().language + + companion object { + private val bannedCharacters = listOf(' ', '_', '-', '(', ')') // Things not to have in enum names + + /** Find a LocaleCode for a [language] as stored in GameSettings */ + fun find(language: String): LocaleCode? { + val languageName = language.filterNot { it in bannedCharacters } + return LocaleCode.entries.firstOrNull { it.name == languageName } + } + + /** Get a Java Locale for a [language] as stored in GameSettings */ + fun getLocale(language: String): Locale = + find(language)?.locale() ?: Locale.getDefault() + + /** Get the fastlane folder name for a [language] as stored in GameSettings */ + fun fastlaneFolder(language: String) = + find(language)?.fastlaneFolder() ?: "en" + + // NumberFormat cache, key: language, value: NumberFormat + private val languageToNumberFormat = mutableMapOf() + + fun getNumberFormatFromLanguage(language: String): NumberFormat = + languageToNumberFormat.getOrPut(language) { + NumberFormat.getInstance(getLocale(language)) + } + } +} diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index 4997d5ff9f..60c49e7367 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -8,7 +8,7 @@ import com.unciv.json.json import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.models.SpyAction import com.unciv.models.metadata.BaseRuleset -import com.unciv.models.metadata.GameSettings.LocaleCode +import com.unciv.models.metadata.LocaleCode import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Event @@ -558,8 +558,7 @@ object TranslationFileWriter { !endWithNewline && translated.endsWith('\n') -> translated.removeSuffix("\n") else -> translated } - val localeCode = LocaleCode.valueOf(language.replace("_","")) - val path = fastlanePath + (localeCode.trueLanguage ?: localeCode.language) + val path = fastlanePath + LocaleCode.fastlaneFolder(language) File(path).mkdirs() File(path + File.separator + fileName).writeText(fileContent) } diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index eda51d7075..3fb72ed792 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -3,7 +3,7 @@ package com.unciv.models.translations import com.badlogic.gdx.Gdx import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.models.metadata.GameSettings.LocaleCode +import com.unciv.models.metadata.LocaleCode import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.unique.Unique import com.unciv.models.stats.Stat @@ -14,7 +14,6 @@ import com.unciv.utils.Log import com.unciv.utils.debug import java.util.Locale import org.jetbrains.annotations.VisibleForTesting -import java.text.NumberFormat /** * This collection holds all translations for the game. @@ -209,11 +208,8 @@ class Translations : LinkedHashMap() { return getText(englishConditionalOrderingString, language, null) } - fun placeConditionalsAfterUnique(language: String): Boolean { - if (get(conditionalUniqueOrderString, language, null)?.get(language) == "before") - return false - return true - } + fun placeConditionalsAfterUnique(language: String) = + get(conditionalUniqueOrderString, language, null)?.get(language) != "before" /** Returns the equivalent of a space in the given language * Defaults to a space if no translation is provided @@ -239,26 +235,6 @@ class Translations : LinkedHashMap() { const val conditionalUniqueOrderString = "ConditionalsPlacement" const val shouldCapitalizeString = "StartWithCapitalLetter" const val effectBeforeCause = "EffectBeforeCause" - - // NumberFormat cache, key: language, value: NumberFormat - private val languageToNumberFormat = mutableMapOf() - - 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)) - } } } @@ -572,5 +548,5 @@ fun Number.tr(): String { // formats number according to given language fun Number.tr(language: String): String { - return Translations.getNumberFormatFromLanguage(language).format(this) + return LocaleCode.getNumberFormatFromLanguage(language).format(this) } diff --git a/fastlane/metadata/android/rus/full_description.txt b/fastlane/metadata/android/rus/full_description.txt deleted file mode 100644 index cfc4f932a5..0000000000 --- a/fastlane/metadata/android/rus/full_description.txt +++ /dev/null @@ -1,14 +0,0 @@ -Удтвореня майпопуларної бавкы за створьованя цвілізацій – фрішноє, манінькоє, без рекламы, навхтема бесплатноє -Стройте свою цівілізацію, изучайте технолоґії, росширяйте свої території, сказіт тай дотовчіт своїх ворогув! - -Понуканя? Багы? Испис задач сьої бавкы увидьте туйкы https://github.com/yairm210/Unciv/issues, ушитка помуч дуже ся чистує! - -Звіданкы? Коментарії? Просто лудно? Прикапчуйте ся ид нам у діскордови https://discord.gg/bjrB4Xw. - -Хотіли бы-сьте помочи перекласти бавку вашым языком? Напишіт ми мейл на yairm210@hotmail.com. - -Знаєте за Grok Java авадь Kotlin? Пойте сюды https://github.com/yairm210/Unciv. - -Світ вас чекат! Ци вчините из свойої цівілізації імперію, яка утримат провбу часом? - -Позволеня на доступ ид сіті мусай дати про ініціовані бавлячом стирьханя тай про мультіплеєрноє бавліня. Ушиткі другі позволеня самі ся додавут API, якый изхосновали-сьме використали про уповісткы за ходы у совмістнуй бавці. Позволеня на доступ ид интернетови мусай дати, обы пак побрати си моды тай музыку, а ще для коректного ладованя мултіплеєрных бавок. Ушиткі другі міньбы не ініціювут ся ігров Unciv. diff --git a/fastlane/metadata/android/rus/short_description.txt b/fastlane/metadata/android/rus/short_description.txt deleted file mode 100644 index 48fd31949d..0000000000 --- a/fastlane/metadata/android/rus/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -4X-стратеґія за створьованя цівілізацій \ No newline at end of file diff --git a/tests/src/com/unciv/logic/TranslationTests.kt b/tests/src/com/unciv/logic/TranslationTests.kt index 8b2d5ef021..0d592a4017 100644 --- a/tests/src/com/unciv/logic/TranslationTests.kt +++ b/tests/src/com/unciv/logic/TranslationTests.kt @@ -5,7 +5,7 @@ import com.badlogic.gdx.Gdx import com.unciv.Constants import com.unciv.UncivGame import com.unciv.models.metadata.GameSettings -import com.unciv.models.metadata.GameSettings.LocaleCode +import com.unciv.models.metadata.LocaleCode import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.stats.Stats