From 69b5cf541a2d2ffd237e1edb92703dd072617607 Mon Sep 17 00:00:00 2001 From: Octavia Togami Date: Sun, 12 Jul 2020 13:44:55 -0700 Subject: [PATCH] Validate number of format placeholders as well --- .../src/main/kotlin/TranslationFileCheck.kt | 43 ++++++++++++++++--- worldedit-core/build.gradle.kts | 1 + 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/kotlin/TranslationFileCheck.kt b/buildSrc/src/main/kotlin/TranslationFileCheck.kt index b5b63ece0a..b9555dc99b 100644 --- a/buildSrc/src/main/kotlin/TranslationFileCheck.kt +++ b/buildSrc/src/main/kotlin/TranslationFileCheck.kt @@ -2,6 +2,8 @@ import groovy.json.JsonSlurper import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileType +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity @@ -9,11 +11,18 @@ import org.gradle.api.tasks.TaskAction import org.gradle.work.ChangeType import org.gradle.work.Incremental import org.gradle.work.InputChanges +import java.io.File import java.text.MessageFormat @Suppress("UnstableApiUsage") abstract class TranslationFileCheck : DefaultTask() { + @get: [ + PathSensitive(PathSensitivity.NONE) + InputFile + ] + abstract val sourceFile: RegularFileProperty + @get:[ Incremental PathSensitive(PathSensitivity.NONE) @@ -23,21 +32,41 @@ abstract class TranslationFileCheck : DefaultTask() { @TaskAction fun checkTranslationFiles(inputChanges: InputChanges) { + val sourceEntries = loadMessageFormats(sourceFile.asFile.get()) + inputChanges.getFileChanges(translationFiles) .asSequence() .filter { it.fileType != FileType.DIRECTORY } .filter { it.changeType != ChangeType.REMOVED } .forEach { change -> - @Suppress("UNCHECKED_CAST") - val entries = JsonSlurper().parse(change.file) as Map - for ((key, value) in entries) { - try { - MessageFormat(value) - } catch (e: IllegalArgumentException) { - error("Entry '$key' in ${change.file} is an invalid translation file: ${e.message}") + val compareEntries = loadMessageFormats(change.file) + for ((key, format) in compareEntries) { + val sourceFormat = sourceEntries[key] + ?: error("Entry '$key' in ${change.file} has no matching format in the source") + val expectedFormatsSize = sourceFormat.formats.size + val actualFormatsSize = format.formats.size + check(expectedFormatsSize == actualFormatsSize) { + "Entry '$key' in ${change.file} has $actualFormatsSize formats instead of $expectedFormatsSize\n" + + "Literal expected: ${sourceFormat.toPattern()}\n" + + "Literal actual: ${format.toPattern()}" } } } } + private fun loadMessageFormats(file: File): Map = + entries(file) + .filterValues { it.isNotEmpty() } + .mapValues { (_, value) -> value.replace("'", "''") } + .mapValues { (key, value) -> + try { + MessageFormat(value) + } catch (e: IllegalArgumentException) { + error("Entry '$key' in $file is an invalid translation file: ${e.message}") + } + } + + @Suppress("UNCHECKED_CAST") + private fun entries(file: File) = JsonSlurper().parse(file) as Map + } diff --git a/worldedit-core/build.gradle.kts b/worldedit-core/build.gradle.kts index ddad585e90..e460adee52 100644 --- a/worldedit-core/build.gradle.kts +++ b/worldedit-core/build.gradle.kts @@ -130,6 +130,7 @@ if (project.hasProperty(crowdinApiKey) && !gradle.startParameter.isOffline) { // allow checking of the source file even without the API key val checkTranslationFiles by tasks.registering(TranslationFileCheck::class) { dependsOn(processResources) + sourceFile.set(i18nSource) translationFiles.from(fileTree(processResources.map { it.destinationDir }) { include("**/lang/**/*.json") })