diff --git a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidator.kt b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidator.kt index 9f4d287..f263132 100644 --- a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidator.kt +++ b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidator.kt @@ -6,16 +6,27 @@ class PlaceholdersValidator { fun validatePlaceholders( mainFilePlaceholders: PlaceholdersForFile, - translatedFilesPlaceholders: List + translatedFilesPlaceholders: List, + ignoredOrderLanguages: Set = emptySet() ): List { val errors = mutableListOf() translatedFilesPlaceholders.forEach { placeholdersForFile -> + val ignoreOrder = ignoredOrderLanguages.any { + placeholdersForFile.filePath.endsWith(it) + } + mainFilePlaceholders.placeholders.forEach { (stringKey, referencePlaceholders) -> if (placeholdersForFile.placeholders.containsKey(stringKey)) { // We don't want to validate not translated strings - val filePlaceholders: List = placeholdersForFile.placeholders[stringKey].orEmpty() + val filePlaceholders: List = placeholdersForFile + .placeholders[stringKey] + .orEmpty() + + val placeholdersMatches = translatedPlaceholderMatchWithReference( + filePlaceholders, referencePlaceholders, ignoreOrder + ) - if (referencePlaceholders != filePlaceholders) { + if (!placeholdersMatches) { errors.add( ValidationError( placeholders = filePlaceholders, @@ -55,6 +66,18 @@ class PlaceholdersValidator { return stringKeyToPlaceholders } + private fun translatedPlaceholderMatchWithReference( + translatedPlaceholders: List, + referencePlaceholders: List, + ignoreOrder: Boolean + ): Boolean { + return translatedPlaceholders.sortIfNeeded(ignoreOrder) == + referencePlaceholders.sortIfNeeded(ignoreOrder) + } + + private fun List.sortIfNeeded(sort: Boolean): List = + if (sort) toMutableList().sorted() else this + /** * For plurals we only take the first string from each language as each language * can have various amount of plurals. diff --git a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorPlugin.kt b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorPlugin.kt index ee680a9..f123a7f 100644 --- a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorPlugin.kt +++ b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorPlugin.kt @@ -4,7 +4,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.FileTree import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty const val EXTENSION_NAME = "placeholdersValidator" const val TASK_NAME = "placeholdersValidatorTask" @@ -23,12 +23,16 @@ abstract class PlaceholdersValidatorPlugin : Plugin { project.tasks.register(TASK_NAME, PlaceholdersValidatorTask::class.java) { task -> task.resourcesDir.set(extension.resourcesDir) task.ignorePlurals.set(extension.ignorePlurals) + task.ignoredOrderLanguages.set(extension.ignoredOrderLanguages) } } } abstract class PlaceholdersValidatorExtension(objects: ObjectFactory) { - val resourcesDir: Property = objects.property(FileTree::class.java) - val ignorePlurals: Property = objects.property(Boolean::class.java) + lateinit var resourcesDir: FileTree + var ignorePlurals: Boolean = false + var ignoredOrderLanguages: SetProperty = objects.setProperty(String::class.java).apply { + set(emptySet()) + } } diff --git a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorTask.kt b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorTask.kt index 0cf519e..b6c9e7e 100644 --- a/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorTask.kt +++ b/src/main/kotlin/com/appunite/placeholdersvalidator/PlaceholdersValidatorTask.kt @@ -8,6 +8,7 @@ import org.gradle.api.file.ConfigurableFileTree import org.gradle.api.file.FileTree import org.gradle.api.internal.file.CompositeFileTree import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File @@ -20,6 +21,8 @@ abstract class PlaceholdersValidatorTask : DefaultTask() { abstract val resourcesDir: Property @get:Input abstract val ignorePlurals: Property + @get:Input + abstract val ignoredOrderLanguages: SetProperty init { description = "Validates placeholders from translated strings.xml files" @@ -49,7 +52,12 @@ abstract class PlaceholdersValidatorTask : DefaultTask() { createStringPlaceholdersMap(it, ignorePlurals) } - val errors = validator.validatePlaceholders(mainFilePlaceholders, translatedFilesPlaceholders) + val errors = validator.validatePlaceholders( + mainFilePlaceholders, + translatedFilesPlaceholders, + ignoredOrderLanguages.get() + ) + if (errors.isNotEmpty()) { val errorMessage = errors.fold("", { acc, error -> acc + error.message }) throw GradleScriptException(errorMessage, Throwable(errorMessage)) diff --git a/src/test/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidatorTest.kt b/src/test/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidatorTest.kt index 7f1e1ff..9bf4877 100644 --- a/src/test/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidatorTest.kt +++ b/src/test/kotlin/com/appunite/placeholdersvalidator/PlaceholderValidatorTest.kt @@ -20,7 +20,9 @@ class PlaceholdersValidatorTest { create() val parsedXml: Node = XmlParser().parse(StringReader(xmlWithPlaceholders)) - val result: Map> = validator.extractPlaceholdersFromXml(parsedXml, ignorePluralsNode = false) + val result: Map> = validator.extractPlaceholdersFromXml( + parsedXml, ignorePluralsNode = false + ) val expected = mutableMapOf>() expected["1"] = listOf("%1${d}s") @@ -74,6 +76,44 @@ class PlaceholdersValidatorTest { ) } + @Test + fun `given ignoring order for german, when wrong order for portuguese and german, then ignore order only for german`() { + create() + + val mainXml = """ + + "Two placeholders %1${d}s %2${d}s" + "String: %s" + + """ + + val wrongOrderFileXml = """ + + "Two placeholders %2${d}s %1${d}s" + "String: %s" + + """ + val mainFilePlaceholders = extractPlaceholdersFrom(mainXml) + val wrongOrderFilePlaceholders = extractPlaceholdersFrom(wrongOrderFileXml) + + val errors = validator.validatePlaceholders( + PlaceholdersForFile(mainFilePlaceholders, "/values/strings.xml"), + listOf( + PlaceholdersForFile(mainFilePlaceholders, "/values-es/strings.xml"), + PlaceholdersForFile(wrongOrderFilePlaceholders, "/values-de/strings.xml"), + PlaceholdersForFile(wrongOrderFilePlaceholders, "/values-pt/strings.xml") + ), + ignoredOrderLanguages = setOf("values-de/strings.xml") + ) + + errors.first().assertPlaceholderError( + key = "1", + placeholders = listOf("%2${d}s", "%1${d}s"), + shouldBePlaceholders = listOf("%1${d}s", "%2${d}s"), + file = "/values-pt/strings.xml" + ) + } + @Test fun `when plurals ignored, then do not parse plurals`() { create()