Translations for the mods (#2228)

* Insert the empty lines to split the nations, policies, etc.

* Read the translations from the mod folders

* Refactoring: Get local link to the translations

* Refactoring: Moving percentage calculations into the TranslationFileWriter

* Refactoring: Caching of the generated strings is removed

* Generate the languages files for the mods

* Minor refactoring

* Using local file storage
This commit is contained in:
Jack Rainy 2020-03-25 10:16:03 +02:00 committed by GitHub
parent abb59ae0d2
commit ea3f7f9dc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 75 deletions

View File

@ -1,6 +1,7 @@
package com.unciv.models.translations
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser
import com.unciv.logic.battle.BattleDamage
@ -17,56 +18,88 @@ import java.lang.reflect.Field
object TranslationFileWriter {
private const val specialNewLineCode = "# This is an empty line "
const val templateFileLocation = "jsons/translations/template.properties"
private val generatedStrings = mutableMapOf<String,MutableSet<String>>()
private const val languageFileLocation = "jsons/translations/%s.properties"
private fun writeByTemplate(language:String, translations: HashMap<String, String>){
fun writeNewTranslationFiles(translations: Translations) {
val templateFile = Gdx.files.internal(templateFileLocation)
val percentages = generateTranslationFiles(translations)
writeLanguagePercentages(percentages)
// try to do the same for the mods
for(modFolder in Gdx.files.local("mods").list())
generateTranslationFiles(translations, modFolder)
// write percentages is not needed: for an individual mod it makes no sense
}
private fun getFileHandle(modFolder: FileHandle?, fileLocation: String) =
if (modFolder != null) modFolder.child(fileLocation)
else Gdx.files.local(fileLocation)
private fun generateTranslationFiles(translations: Translations, modFolder: FileHandle? = null): HashMap<String, Int> {
// read the template
val templateFile = getFileHandle(modFolder, templateFileLocation)
val linesFromTemplates = mutableListOf<String>()
linesFromTemplates.addAll(templateFile.reader().readLines())
generateStringsFromJSONs()
if (templateFile.exists())
linesFromTemplates.addAll(templateFile.reader().readLines())
// read the JSON files
val generatedStrings = generateStringsFromJSONs(modFolder)
for (key in generatedStrings.keys) {
linesFromTemplates.add("\n#################### Lines from $key.json ####################\n")
linesFromTemplates.addAll(generatedStrings.getValue(key))
}
val stringBuilder = StringBuilder()
for(line in linesFromTemplates){
if(!line.contains(" = ")){ // copy as-is
stringBuilder.appendln(line)
continue
}
val translationKey = line.split(" = ")[0].replace("\\n","\n")
var translationValue = ""
if(translations.containsKey(translationKey)) translationValue = translations[translationKey]!!
else stringBuilder.appendln(" # Requires translation!")
val lineToWrite = translationKey.replace("\n","\\n") +
" = "+ translationValue.replace("\n","\\n")
stringBuilder.appendln(lineToWrite)
}
Gdx.files.local("jsons/translations/$language.properties")
.writeString(stringBuilder.toString(),false, TranslationFileReader.charset)
}
fun writeNewTranslationFiles(translations: Translations) {
var countOfTranslatableLines = 0
val countOfTranslatedLines = HashMap<String, Int>()
// iterate through all available languages
for (language in translations.getLanguages()) {
val languageHashmap = HashMap<String, String>()
var translationsOfThisLanguage = 0
val stringBuilder = StringBuilder()
for (translation in translations.values) {
if (translation.containsKey(language))
languageHashmap[translation.entry] = translation[language]!!
for (line in linesFromTemplates) {
if (line.contains(" = ")) {
// count translatable lines only once (e.g. for English)
if (language == "English") countOfTranslatableLines++
} else {
// small hack to insert empty lines
if (line.startsWith(specialNewLineCode))
stringBuilder.appendln()
else // copy as-is
stringBuilder.appendln(line)
continue
}
val translationKey = line.split(" = ")[0].replace("\\n", "\n")
var translationValue = ""
val translationEntry = translations[translationKey]
if (translationEntry != null && translationEntry.containsKey(language)) {
translationValue = translationEntry[language]!!
translationsOfThisLanguage++
} else stringBuilder.appendln(" # Requires translation!")
val lineToWrite = translationKey.replace("\n", "\\n") +
" = " + translationValue.replace("\n", "\\n")
stringBuilder.appendln(lineToWrite)
}
writeByTemplate(language, languageHashmap)
countOfTranslatedLines[language] = translationsOfThisLanguage
val fileWriter = getFileHandle(modFolder,languageFileLocation.format(language))
fileWriter.writeString(stringBuilder.toString(), false, TranslationFileReader.charset)
}
writeLanguagePercentages(translations)
// Calculate the percentages of translations
// It should be done after the loop of languages, since the countOfTranslatableLines is not known in the 1st iteration
for (key in countOfTranslatedLines.keys)
countOfTranslatedLines[key] = countOfTranslatedLines.getValue(key)*100/countOfTranslatableLines
return countOfTranslatedLines
}
private fun writeLanguagePercentages(translations: Translations){
val percentages = translations.calculatePercentageCompleteOfLanguages()
private fun writeLanguagePercentages(percentages: HashMap<String,Int>){
val stringBuilder = StringBuilder()
for(entry in percentages){
stringBuilder.appendln(entry.key+" = "+entry.value)
@ -74,42 +107,44 @@ object TranslationFileWriter {
Gdx.files.local(TranslationFileReader.percentagesFileLocation).writeString(stringBuilder.toString(),false)
}
private fun generateTutorialsStrings(): Collection<String> {
if (generatedStrings.containsKey("Tutorials"))
return generatedStrings.getValue("Tutorials")
generatedStrings["Tutorials"] = mutableSetOf()
val tutorialsStrings = generatedStrings["Tutorials"]
private fun generateTutorialsStrings(): MutableSet<String> {
val tutorialsStrings = mutableSetOf<String>()
val tutorials = JsonParser().getFromJson(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json")
var uniqueIndexOfNewLine = 0
for (tutorial in tutorials) {
for (str in tutorial.value)
if (str != "") tutorialsStrings!!.add("$str = ")
if (str != "") tutorialsStrings.add("$str = ")
// This is a small hack to insert multiple /n into the set, which can't contain identical lines
tutorialsStrings.add("$specialNewLineCode ${uniqueIndexOfNewLine++}")
}
return tutorialsStrings!!
return tutorialsStrings
}
// used for unit test only
fun getGeneratedStringsSize(): Int {
if (generatedStrings.isEmpty())
generateStringsFromJSONs()
return generatedStrings.values.sumBy { it.size }
return generateStringsFromJSONs().values.sumBy { // exclude empty lines
it.count{ line: String -> !line.startsWith(specialNewLineCode) } }
}
private fun generateStringsFromJSONs() {
private fun generateStringsFromJSONs(modFolder: FileHandle? = null): Map<String, MutableSet<String>> {
if (generatedStrings.isNotEmpty())
return // do not regenerate if the strings are ready
// Using LinkedHashMap (instead of HashMap) is important to maintain the order of sections in the translation file
val generatedStrings = LinkedHashMap<String, MutableSet<String>>()
var uniqueIndexOfNewLine = 0
val jsonParser = JsonParser()
val folderHandler = Gdx.files.internal("jsons")
val folderHandler = getFileHandle(modFolder,"jsons")
val listOfJSONFiles = folderHandler.list{file -> file.name.endsWith(".json", true)}
for (jsonFile in listOfJSONFiles)
{
val filename = jsonFile.nameWithoutExtension()
// Tutorials are a bit special
if (filename == "Tutorials") {
generateTutorialsStrings()
generatedStrings[filename] = generateTutorialsStrings()
continue
}
@ -165,9 +200,13 @@ object TranslationFileWriter {
}
if (array is kotlin.Array<*>)
for (element in array)
for (element in array) {
serializeElement(element!!) // let's serialize the strings recursively
// This is a small hack to insert multiple /n into the set, which can't contain identical lines
resultStrings!!.add("$specialNewLineCode ${uniqueIndexOfNewLine++}")
}
}
return generatedStrings
}
private fun isFieldTranslatable(field: Field, fieldValue: Any?): Boolean {

View File

@ -44,6 +44,13 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
return
}
// try to load the translations from the mods
for(modFolder in Gdx.files.local("mods").list()) {
val modTranslationFile = modFolder.child(translationFileName)
if (modTranslationFile.exists())
languageTranslations.putAll(TranslationFileReader.read(modTranslationFile.path()))
}
for (translation in languageTranslations) {
if (!containsKey(translation.key))
this[translation.key] = TranslationEntry(translation.key)
@ -62,6 +69,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
tryReadTranslationForLanguage(UncivGame.Current.settings.language)
}
// This function is too strange for me, however, let's keep it "as is" for now. - JackRainy
private fun getLanguagesWithTranslationFile(): List<String> {
val languages = ArrayList<String>()
@ -114,30 +122,6 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
println("Loading percent complete of languages - "+translationFilesTime+"ms")
}
fun calculatePercentageCompleteOfLanguages():HashMap<String,Int> {
val percentComplete = HashMap<String,Int>()
val translationStart = System.currentTimeMillis()
var allTranslations = TranslationFileWriter.getGeneratedStringsSize()
Gdx.files.internal(TranslationFileWriter.templateFileLocation)
.reader().forEachLine { if(it.contains(" = ")) allTranslations+=1 }
for(language in getLanguagesWithTranslationFile()){
val translationFileName = "jsons/translations/$language.properties"
var translationsOfThisLanguage=0
Gdx.files.internal(translationFileName).reader()
.forEachLine { if(it.contains(" = ") && !it.endsWith(" = "))
translationsOfThisLanguage+=1 }
percentComplete[language] = translationsOfThisLanguage*100/allTranslations
}
val translationFilesTime = System.currentTimeMillis() - translationStart
println("Calculating percentage complete of languages - "+translationFilesTime+"ms")
return percentComplete
}
companion object {
fun translateBonusOrPenalty(unique:String): String {
val regexResult = Regex("""(Bonus|Penalty) vs (.*) (\d*)%""").matchEntire(unique)