Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
```

Expand All @@ -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.
Expand Down Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class Localization(val resources: List<Resource>) {
// 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
Expand Down Expand Up @@ -69,9 +69,17 @@ data class Localization(val resources: List<Resource>) {
// 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<Resource.Entry>()

// starting to accumulate quantities for plural, reset to null if the next row is another plural or key
Expand All @@ -82,7 +90,7 @@ data class Localization(val resources: List<Resource>) {
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 "##")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()) }
}
Expand Down Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}
2 changes: 1 addition & 1 deletion plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repositories {
}

group 'cz.ackee.localizer'
version '1.3.0'
version '1.4.0'

def targetVersion = "2022.3.1.18"

Expand Down
Loading