diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/genericStructuredFile/in/GenericStructuredProcessor.kt b/backend/data/src/main/kotlin/io/tolgee/formats/genericStructuredFile/in/GenericStructuredProcessor.kt index 518ae7d6be..7d6164c6ed 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/genericStructuredFile/in/GenericStructuredProcessor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/genericStructuredFile/in/GenericStructuredProcessor.kt @@ -3,9 +3,9 @@ package io.tolgee.formats.genericStructuredFile.`in` import io.tolgee.formats.ImportFileProcessor import io.tolgee.formats.MessageConvertorResult import io.tolgee.formats.allPluralKeywords -import io.tolgee.formats.i18next.ParsedI18nextKey -import io.tolgee.formats.i18next.PluralsI18nextKeyParser import io.tolgee.formats.importCommon.ImportFormat +import io.tolgee.formats.importCommon.ParsedPluralsKey +import io.tolgee.formats.importCommon.PluralsKeyParser import io.tolgee.service.dataImport.processors.FileProcessorContext class GenericStructuredProcessor( @@ -15,8 +15,6 @@ class GenericStructuredProcessor( private val languageTag: String? = null, private val format: ImportFormat, ) : ImportFileProcessor() { - private val keyParser = PluralsI18nextKeyParser() - override fun process() { data.preprocess().import("") } @@ -41,44 +39,49 @@ class GenericStructuredProcessor( return this.map { it.preprocess() } } - private fun Map<*, *>.groupByPlurals(keyRegex: Regex): Map>> { + private fun Any?.parsePluralsKey(keyParser: PluralsKeyParser): ParsedPluralsKey? { + val key = this as? String ?: return null + return keyParser.parse(key).takeIf { + it.key != null && it.plural in allPluralKeywords + } ?: ParsedPluralsKey(null, null, key) + } + + private fun Map<*, *>.groupByPlurals( + keyParser: PluralsKeyParser + ): Map>> { return this.entries.mapIndexedNotNull { idx, (key, value) -> - if (key !is String) { - context.fileEntity.addKeyIsNotStringIssue(key.toString(), idx) - return@mapIndexedNotNull null + key.parsePluralsKey(keyParser)?.let { it to value }.also { + if (it == null) { + context.fileEntity.addKeyIsNotStringIssue(key.toString(), idx) + } } - val default = ParsedI18nextKey(null, null, key) - - val match = keyRegex.find(key) ?: return@mapIndexedNotNull default to value - val parsedKey = keyParser.parse(match) + }.groupBy { (parsedKey, _) -> parsedKey.key }.toMap() + } - if (parsedKey?.key == null || parsedKey.plural == null || parsedKey.plural !in allPluralKeywords) { - return@mapIndexedNotNull default to value - } + private fun List>.useOriginalKey(): List> { + return map { (parsedKey, value) -> + parsedKey.originalKey to value + } + } - return@mapIndexedNotNull parsedKey to value - }.groupBy { (parsedKey, _) -> - parsedKey.key - }.toMap() + private fun List>.usePluralsKey(commonKey: String): List> { + return listOf(commonKey to this.associate { (parsedKey, value) -> + parsedKey.plural to value + }) } private fun Map<*, *>.preprocessMap(): Map<*, *> { - if (format.pluralsViaSuffixesRegex == null) { + if (format.pluralsViaSuffixesParser == null) { return this.mapValues { (_, value) -> value.preprocess() } } - val plurals = this.groupByPlurals(format.pluralsViaSuffixesRegex) + val plurals = this.groupByPlurals(format.pluralsViaSuffixesParser) - return plurals.flatMap { (key, values) -> - if (key == null || values.size < 2) { - // Fallback for non-plural keys - values.map { (parsedKey, value) -> - parsedKey.fullMatch to value - } + return plurals.flatMap { (commonKey, values) -> + if (commonKey == null || values.size < 2) { + values.useOriginalKey() } else { - listOf(key to values.map { (parsedKey, value) -> - parsedKey.plural to value - }.toMap()) + values.usePluralsKey(commonKey) } }.toMap() } diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/getGroupOrNull.kt b/backend/data/src/main/kotlin/io/tolgee/formats/getGroupOrNull.kt new file mode 100644 index 0000000000..8fddc3ee5a --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/formats/getGroupOrNull.kt @@ -0,0 +1,12 @@ +package io.tolgee.formats + +fun MatchGroupCollection.getGroupOrNull(name: String): MatchGroup? { + try { + return this[name] + } catch (e: IllegalArgumentException) { + if (e.message?.contains("No group with name") != true) { + throw e + } + return null + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextKey.kt b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextKey.kt deleted file mode 100644 index d8d523eb0c..0000000000 --- a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextKey.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.tolgee.formats.i18next - -data class ParsedI18nextKey( - val key: String? = null, - val plural: String? = null, - val fullMatch: String, -) diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/PluralsI18nextKeyParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/PluralsI18nextKeyParser.kt deleted file mode 100644 index 90cbc7cc17..0000000000 --- a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/PluralsI18nextKeyParser.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.tolgee.formats.i18next - -class PluralsI18nextKeyParser { - fun parse(match: MatchResult): ParsedI18nextKey? { - return ParsedI18nextKey( - key = match.groups.getGroupOrNull("key")?.value, - plural = match.groups.getGroupOrNull("plural")?.value, - fullMatch = match.value, - ) - } - - // FIXME: move somewhere shared - private fun MatchGroupCollection.getGroupOrNull(name: String): MatchGroup? { - try { - return this[name] - } catch (e: IllegalArgumentException) { - if (e.message?.contains("No group with name") != true) { - throw e - } - return null - } - } -} diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/I18nextParameterParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/I18nextParameterParser.kt similarity index 50% rename from backend/data/src/main/kotlin/io/tolgee/formats/i18next/I18nextParameterParser.kt rename to backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/I18nextParameterParser.kt index d86b479fea..085fb49fe5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/I18nextParameterParser.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/I18nextParameterParser.kt @@ -1,4 +1,6 @@ -package io.tolgee.formats.i18next +package io.tolgee.formats.i18next.`in` + +import io.tolgee.formats.getGroupOrNull class I18nextParameterParser { fun parse(match: MatchResult): ParsedI18nextParam? { @@ -9,16 +11,4 @@ class I18nextParameterParser { fullMatch = match.value, ) } - -// FIXME: move somewhere shared - private fun MatchGroupCollection.getGroupOrNull(name: String): MatchGroup? { - try { - return this[name] - } catch (e: IllegalArgumentException) { - if (e.message?.contains("No group with name") != true) { - throw e - } - return null - } - } } diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextParam.kt b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/ParsedI18nextParam.kt similarity index 79% rename from backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextParam.kt rename to backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/ParsedI18nextParam.kt index 38f3864b60..dfe969ad76 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/ParsedI18nextParam.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/ParsedI18nextParam.kt @@ -1,4 +1,4 @@ -package io.tolgee.formats.i18next +package io.tolgee.formats.i18next.`in` data class ParsedI18nextParam( val key: String? = null, diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/PluralsI18nextKeyParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/PluralsI18nextKeyParser.kt new file mode 100644 index 0000000000..87163e558f --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/formats/i18next/in/PluralsI18nextKeyParser.kt @@ -0,0 +1,16 @@ +package io.tolgee.formats.i18next.`in` + +import io.tolgee.formats.getGroupOrNull +import io.tolgee.formats.importCommon.ParsedPluralsKey +import io.tolgee.formats.importCommon.PluralsKeyParser + +class PluralsI18nextKeyParser(private val keyRegex: Regex) : PluralsKeyParser { + override fun parse(key: String): ParsedPluralsKey { + val match = keyRegex.find(key) + return ParsedPluralsKey( + key = match?.groups?.getGroupOrNull("key")?.value, + plural = match?.groups?.getGroupOrNull("plural")?.value, + originalKey = key, + ) + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt index 98b61e2fd9..c59c828578 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ImportFormat.kt @@ -11,14 +11,14 @@ import io.tolgee.formats.po.`in`.PoToIcuMessageConvertor enum class ImportFormat( val fileFormat: ImportFileFormat, val pluralsViaNesting: Boolean = false, - val pluralsViaSuffixesRegex: Regex? = null, + val pluralsViaSuffixesParser: PluralsKeyParser? = null, val messageConvertorOrNull: ImportMessageConvertor? = null, val rootKeyIsLanguageTag: Boolean = false, ) { JSON_I18NEXT( ImportFileFormat.JSON, messageConvertorOrNull = GenericMapPluralImportRawDataConvertor { I18nextToIcuPlaceholderConvertor() }, - pluralsViaSuffixesRegex = I18nextToIcuPlaceholderConvertor.I18NEXT_PLURAL_SUFFIX_REGEX, + pluralsViaSuffixesParser = I18nextToIcuPlaceholderConvertor.I18NEXT_PLURAL_SUFFIX_KEY_PARSER, ), JSON_ICU( ImportFileFormat.JSON, diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ParsedPluralsKey.kt b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ParsedPluralsKey.kt new file mode 100644 index 0000000000..13af6cdf9a --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/ParsedPluralsKey.kt @@ -0,0 +1,7 @@ +package io.tolgee.formats.importCommon + +data class ParsedPluralsKey( + val key: String? = null, + val plural: String? = null, + val originalKey: String, +) diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/PluralsKeyParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/PluralsKeyParser.kt new file mode 100644 index 0000000000..4f08f13a53 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/formats/importCommon/PluralsKeyParser.kt @@ -0,0 +1,5 @@ +package io.tolgee.formats.importCommon + +interface PluralsKeyParser { + fun parse(key: String): ParsedPluralsKey +} diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/I18nextToIcuPlaceholderConvertor.kt b/backend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/I18nextToIcuPlaceholderConvertor.kt index 4493400811..5853275e5d 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/I18nextToIcuPlaceholderConvertor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/paramConvertors/in/I18nextToIcuPlaceholderConvertor.kt @@ -2,7 +2,8 @@ package io.tolgee.formats.paramConvertors.`in` import io.tolgee.formats.ToIcuPlaceholderConvertor import io.tolgee.formats.escapeIcu -import io.tolgee.formats.i18next.I18nextParameterParser +import io.tolgee.formats.i18next.`in`.I18nextParameterParser +import io.tolgee.formats.i18next.`in`.PluralsI18nextKeyParser class I18nextToIcuPlaceholderConvertor : ToIcuPlaceholderConvertor { private val parser = I18nextParameterParser() @@ -64,5 +65,7 @@ class I18nextToIcuPlaceholderConvertor : ToIcuPlaceholderConvertor { val I18NEXT_PLURAL_SUFFIX_REGEX = """^(?\w+)_(?\w+)$""".toRegex() + val I18NEXT_PLURAL_SUFFIX_KEY_PARSER = PluralsI18nextKeyParser(I18NEXT_PLURAL_SUFFIX_REGEX) + } } diff --git a/backend/data/src/main/kotlin/io/tolgee/formats/po/in/CLikeParameterParser.kt b/backend/data/src/main/kotlin/io/tolgee/formats/po/in/CLikeParameterParser.kt index d7449d0852..b6673cce14 100644 --- a/backend/data/src/main/kotlin/io/tolgee/formats/po/in/CLikeParameterParser.kt +++ b/backend/data/src/main/kotlin/io/tolgee/formats/po/in/CLikeParameterParser.kt @@ -1,5 +1,7 @@ package io.tolgee.formats.po.`in` +import io.tolgee.formats.getGroupOrNull + class CLikeParameterParser { fun parse(match: MatchResult): ParsedCLikeParam? { val specifierGroup = match.groups["specifier"] @@ -18,15 +20,4 @@ class CLikeParameterParser { fullMatch = match.value, ) } - - private fun MatchGroupCollection.getGroupOrNull(name: String): MatchGroup? { - try { - return this[name] - } catch (e: IllegalArgumentException) { - if (e.message?.contains("No group with name") != true) { - throw e - } - return null - } - } }