diff --git a/README.md b/README.md index aa75ac1..18ec44c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ The structure of this JSON is described in the example: "resourcesFolderPath": "libraries/translations/src/main/res", // Relative path to your "res" folder with respect to json configuration path "supportEmptyStrings": false, // Enables to support empty strings in the localization sheet "resourcesStructure" : "ANDROID", // Structure of the generated resources files, could be ANDROID or MOKO_RESOURCES - "escapeQuotes" : true // Whether escape quotes or not, disable this when working with Compose Resources + "escapeQuotes" : true, // Whether escape quotes or not, disable this when working with Compose Resources + "keyColumn" : "key_android" // Column name used as the Android resource key, optional, defaults to "key_android" } ``` @@ -47,7 +48,7 @@ a previous behaviour. The localization plugin of course depends on a predefined Google sheet structure. These are the rules the spreadsheet must follow for the plugin to work correctly: 1. The optional "Section" column. Based on values from this column the plugin visually separates the logical chunks of your resources (usually screens).The row containing section name shouldn't contain anything else. -2. The column "key_android" - the resource keys for your strings.xml should be put here +2. The column with resource keys for your strings.xml (defaults to `"key_android"`, configurable via `keyColumn`) 3. Locale columns (e.g. "en", "de", "cs") - contain actual translations. 4. Base on config file they are mapped to resource files in corresponding to the `resourcesStructure` type. - For `DEFAULT` are created files like `values/strings.xml`, `values-cs/strings.xml`, `values-fr-rFR/strings.xml` etc. @@ -89,6 +90,9 @@ test it in your project with the localizerSettings.xml like this: ``` ## Changelog +- `1.4.0` + - Support for custom key column name (defaults to "key_android") + - Fix mapping columns - `1.3.0` - Support for Compose Resources quote escaping strategy - `1.2.2` diff --git a/core/src/main/kotlin/cz/ackee/localizer/plugin/core/configuration/LocalizationConfig.kt b/core/src/main/kotlin/cz/ackee/localizer/plugin/core/configuration/LocalizationConfig.kt index 7435d44..41bc7a6 100644 --- a/core/src/main/kotlin/cz/ackee/localizer/plugin/core/configuration/LocalizationConfig.kt +++ b/core/src/main/kotlin/cz/ackee/localizer/plugin/core/configuration/LocalizationConfig.kt @@ -20,4 +20,5 @@ data class LocalizationConfig( @SerialName("supportEmptyStrings") val supportEmptyStrings: Boolean = false, @SerialName("resourcesStructure") val resourcesStructure: ResourcesStructure = ResourcesStructure.ANDROID, @SerialName("escapeQuotes") val escapeQuotes: Boolean = true, + @SerialName("keyColumn") val keyColumn: String = "key_android", ) diff --git a/core/src/main/kotlin/cz/ackee/localizer/plugin/core/localization/Localization.kt b/core/src/main/kotlin/cz/ackee/localizer/plugin/core/localization/Localization.kt index d6e87f8..8236385 100644 --- a/core/src/main/kotlin/cz/ackee/localizer/plugin/core/localization/Localization.kt +++ b/core/src/main/kotlin/cz/ackee/localizer/plugin/core/localization/Localization.kt @@ -18,7 +18,7 @@ data class Localization(val resources: List) { // looking for valid column IDs (section column, android key column and supported languages keys) val validColumnIds = response.values[0].mapIndexed { index, name -> if (name.contains("section", ignoreCase = true) || - name.contains("android", ignoreCase = true) || + name == configuration.keyColumn || configuration.languageMapping.keys.contains(name)) { if (name.contains("section", ignoreCase = true)) { sectionIndex = index @@ -69,9 +69,17 @@ data class Localization(val resources: List) { // iterating through all languages (columns with values) (filteredValues[0] as XmlRow.Header).cells .drop(if (sectionIndex == null) 1 else 2) // drop key column and section column if exists - .map { configuration.languageMapping[it] } // get the suffix from mapping based on column key - .mapIndexed { index, suffix -> - // now we have index for particular language and its suffix + .mapIndexedNotNull { valueIndex, name -> + // keep only real languages (present in mapping), dropping any other column; + // valueIndex preserves the column position so the value lookup stays aligned + if (configuration.languageMapping.containsKey(name)) { + valueIndex to configuration.languageMapping[name] + } else { + null + } + } + .map { (valueIndex, suffix) -> + // now we have the value offset for a particular language and its suffix val entries = mutableListOf() // starting to accumulate quantities for plural, reset to null if the next row is another plural or key @@ -82,7 +90,7 @@ data class Localization(val resources: List) { if (row is XmlRow.Key) { // if this is a key row val key = row.cells[0] // take its key - val value = row.cells.getOrNull(index + 1) + val value = row.cells.getOrNull(valueIndex + 1) ?: "" // take its value for this language (+1 due to key) if (key.contains("##")) { // if this is a plural string (contains "##") diff --git a/core/src/test/kotlin/cz/ackee/localizer/plugin/core/configuration/ConfigurationParserImplTest.kt b/core/src/test/kotlin/cz/ackee/localizer/plugin/core/configuration/ConfigurationParserImplTest.kt index 3ccfb00..2f2eb04 100644 --- a/core/src/test/kotlin/cz/ackee/localizer/plugin/core/configuration/ConfigurationParserImplTest.kt +++ b/core/src/test/kotlin/cz/ackee/localizer/plugin/core/configuration/ConfigurationParserImplTest.kt @@ -41,6 +41,7 @@ class ConfigurationParserImplTest { includeSupportEmptyStrings: Boolean = true, includeResourcesStructure: Boolean = true, includeEscapeQuotes: Boolean = true, + includeKeyColumn: Boolean = true, ): File { val serializedConfig = buildJsonObject { put("fileId", config.fileId) @@ -61,6 +62,9 @@ class ConfigurationParserImplTest { if (includeEscapeQuotes) { put("escapeQuotes", config.escapeQuotes) } + if (includeKeyColumn) { + put("keyColumn", config.keyColumn) + } }.toString() return temporaryFolder.newFile("config.json").also { it.writeText(serializedConfig.trimIndent()) } } @@ -108,4 +112,23 @@ class ConfigurationParserImplTest { actualConfig.escapeQuotes shouldBeEqualTo false } + + @Test + fun `Default to keyColumn=key_android if the attribute is missing`() { + val file = createTestConfigurationFileFrom(LocalizationConfig(), includeKeyColumn = false) + + val actualConfig = underTest.parse(file.absolutePath) + + actualConfig.keyColumn shouldBeEqualTo "key_android" + } + + @Test + fun `Parse custom keyColumn correctly`() { + val config = LocalizationConfig(keyColumn = "key_custom") + val file = createTestConfigurationFileFrom(config) + + val actualConfig = underTest.parse(file.absolutePath) + + actualConfig.keyColumn shouldBeEqualTo "key_custom" + } } diff --git a/core/src/test/kotlin/cz/ackee/localizer/plugin/core/localization/LocalizationTest.kt b/core/src/test/kotlin/cz/ackee/localizer/plugin/core/localization/LocalizationTest.kt index 06209c7..9f2d0bd 100644 --- a/core/src/test/kotlin/cz/ackee/localizer/plugin/core/localization/LocalizationTest.kt +++ b/core/src/test/kotlin/cz/ackee/localizer/plugin/core/localization/LocalizationTest.kt @@ -54,4 +54,43 @@ class LocalizationTest { localization ) } + + @Test + fun shouldDropUnmappedColumnsAndKeepValueAlignment() { + val response = GoogleSheetResponse( + range = "", + majorDimension = "", + values = listOf( + listOf("section", "key_android", "key_android_note", "EN", "CS"), + listOf("Section1"), + listOf("", "key1.android", "ignored1", "Value1EN", "Value1CS"), + listOf("", "key2.android", "ignored2", "Value2EN", "Value2CS") + ) + ) + val localization = Localization.fromGoogleResponse( + response = response, + configuration = LocalizationConfig(languageMapping = mapOf("EN" to null, "CS" to "cs-cs")) + ) + assertEquals( + Localization( + listOf( + Localization.Resource( + null, listOf( + Localization.Resource.Entry.Section("Section1"), + Localization.Resource.Entry.Key("key1.android", "Value1EN"), + Localization.Resource.Entry.Key("key2.android", "Value2EN") + ) + ), + Localization.Resource( + "cs-cs", listOf( + Localization.Resource.Entry.Section("Section1"), + Localization.Resource.Entry.Key("key1.android", "Value1CS"), + Localization.Resource.Entry.Key("key2.android", "Value2CS") + ) + ) + ) + ), + localization + ) + } } diff --git a/plugin/build.gradle b/plugin/build.gradle index 38a2cca..6ed8796 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -15,7 +15,7 @@ repositories { } group 'cz.ackee.localizer' -version '1.3.0' +version '1.4.0' def targetVersion = "2022.3.1.18"