Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Explicit isPlural parameter in the resolvable import endpoint #2959

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ import io.tolgee.dtos.request.translation.ImportKeysDto
import io.tolgee.dtos.request.translation.importKeysResolvable.ImportKeysResolvableDto
import io.tolgee.dtos.request.validators.exceptions.ValidationException
import io.tolgee.exceptions.NotFoundException
import io.tolgee.hateoas.key.KeyImportResolvableResultModel
import io.tolgee.hateoas.key.KeyModel
import io.tolgee.hateoas.key.KeyModelAssembler
import io.tolgee.hateoas.key.KeySearchResultModelAssembler
import io.tolgee.hateoas.key.KeySearchSearchResultModel
import io.tolgee.hateoas.key.KeyWithDataModel
import io.tolgee.hateoas.key.KeyWithDataModelAssembler
import io.tolgee.hateoas.key.KeyWithScreenshotsModelAssembler
import io.tolgee.hateoas.key.*
import io.tolgee.hateoas.language.LanguageModel
import io.tolgee.hateoas.language.LanguageModelAssembler
import io.tolgee.hateoas.screenshot.ScreenshotModelAssembler
Expand Down Expand Up @@ -56,17 +49,7 @@ import org.springframework.hateoas.PagedModel
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*

@Suppress("MVCPathVariableInspection")
@RestController
Expand Down Expand Up @@ -167,7 +150,15 @@ class KeyController(
key.checkInProject()
checkNamespaceFeature(dto.namespace)
keyService.edit(id, dto)
val view = KeyView(key.id, key.name, key?.namespace?.name, key.keyMeta?.description, key.keyMeta?.custom)
val view =
KeyView(
key.id,
key.name,
key?.namespace?.name,
key.keyMeta?.description,
key.keyMeta?.custom,
key.isPlural,
)
return keyModelAssembler.toModel(view)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.tolgee.activity.data.ActivityType
import io.tolgee.constants.Message
import io.tolgee.dtos.request.key.ComplexEditKeyDto
import io.tolgee.exceptions.NotFoundException
import io.tolgee.formats.PossibleConvertToIcuPluralResult
import io.tolgee.hateoas.key.KeyWithDataModel
import io.tolgee.hateoas.key.KeyWithDataModelAssembler
import io.tolgee.model.Language
Expand Down Expand Up @@ -129,7 +130,7 @@ class KeyComplexEditHelper(

if (isIsPluralChanged) {
key.isPlural = dto.isPlural!!
key.pluralArgName = dto.pluralArgName ?: key.pluralArgName
key.pluralArgName = newPluralArgName
translationService.onKeyIsPluralChanged(
mapOf(key.id to newPluralArgName),
dto.isPlural!!,
Expand Down Expand Up @@ -198,7 +199,9 @@ class KeyComplexEditHelper(
)

val modifiedTranslations = getModifiedTranslationsByTag()
val normalizedPlurals = validateAndNormalizePlurals(modifiedTranslations)
val conversionResult = validateAndNormalizePlurals(modifiedTranslations)
val normalizedPlurals = conversionResult.convertedStrings
newPluralArgName = conversionResult.argName

val existingTranslationsByTag = getExistingTranslationsByTag()
val oldTranslations =
Expand All @@ -221,17 +224,23 @@ class KeyComplexEditHelper(
}
}

private fun validateAndNormalizePlurals(modifiedTranslations: Map<Language, String?>): Map<Language, String?> {
if (newIsPlural) {
return translationService.validateAndNormalizePlurals(modifiedTranslations, newPluralArgName)
}
return modifiedTranslations
private fun validateAndNormalizePlurals(modifiedTranslations: Map<Language, String?>):
PossibleConvertToIcuPluralResult<Language> {
return translationService.validateAndNormalizePlurals(modifiedTranslations, newIsPlural, explicitPluralArgName)
}

private val newPluralArgName: String? by lazy {
private val explicitPluralArgName: String? by lazy {
dto.pluralArgName ?: key.pluralArgName
}

private var newPluralArgName: String? = null
get() {
if (field == null) {
return explicitPluralArgName
}
return field
}

private fun getExistingTranslationsByTag() =
existingTranslations.map { languageByTag(it.key) to it.value.text }.toMap()

Expand Down
2 changes: 2 additions & 0 deletions backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ open class KeyModel(
val description: String?,
@Schema(description = "Custom values of the key")
val custom: Map<String, Any?>?,
@Schema(description = "If the translations of this key are different for each plural form")
val isPlural: Boolean,
) : RepresentationModel<KeyModel>(), Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ class KeyModelAssembler : RepresentationModelAssemblerSupport<KeyView, KeyModel>
namespace = view.namespace,
description = view.description,
custom = view.custom as? Map<String, Any?>?,
isPlural = view.isPlural,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import io.tolgee.dtos.request.KeyInScreenshotPositionDto
import io.tolgee.dtos.request.key.ComplexEditKeyDto
import io.tolgee.dtos.request.key.KeyScreenshotDto
import io.tolgee.exceptions.FileStoreException
import io.tolgee.fixtures.andAssertThatJson
import io.tolgee.fixtures.andHasErrorMessage
import io.tolgee.fixtures.andIsBadRequest
import io.tolgee.fixtures.andIsForbidden
import io.tolgee.fixtures.andIsOk
import io.tolgee.fixtures.andPrettyPrint
import io.tolgee.fixtures.isValidId
import io.tolgee.fixtures.node
import io.tolgee.fixtures.*
import io.tolgee.model.enums.AssignableTranslationState
import io.tolgee.model.enums.Scope
import io.tolgee.model.enums.TranslationState
Expand Down Expand Up @@ -448,6 +441,19 @@ class KeyControllerComplexEditTest : ProjectAuthControllerTest("/v2/projects/")
}
}

@ProjectApiKeyAuthTestMethod
@Test
fun `updates correctly guesses pluralArgName`() {
performIsPluralModification(
translations = mapOf(
"en" to "{this_should_be_guessed, plural, one {one} other {other}}",
)
).andIsOk.andPrettyPrint.andAssertThatJson {
node("isPlural").isEqualTo(true)
node("pluralArgName").isEqualTo("this_should_be_guessed")
}
}

@ProjectApiKeyAuthTestMethod(
scopes = [
Scope.TRANSLATIONS_EDIT,
Expand Down Expand Up @@ -512,14 +518,15 @@ class KeyControllerComplexEditTest : ProjectAuthControllerTest("/v2/projects/")
).andIsBadRequest.andHasErrorMessage(Message.CUSTOM_VALUES_JSON_TOO_LONG)
}

private fun performIsPluralModification(): ResultActions {
private fun performIsPluralModification(translations: Map<String, String> = emptyMap()): ResultActions {
val keyName = "super_key"

return performProjectAuthPut(
"keys/${testData.keyWithReferences.id}/complex-update",
ComplexEditKeyDto(
name = keyName,
isPlural = true,
translations = translations,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ data class KeyView(
val namespace: String?,
val description: String?,
val custom: Any?,
val isPlural: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ class ImportKeysResolvableItemDto(
description = "Object mapping language tag to translation",
)
val translations: Map<String, ImportTranslationResolvableDto?> = mapOf(),

@Schema(
description = "Whether key is pluralized.\n\n" +
"If null, it will be auto-detected (if any translation contains valid ICU plural syntax, " +
"the key is considered plural)." +
"However, when auto-detected only from not-plural to plural transition is allowed.\n\n" +
"When provided, both way transition is enabled.",
)
val isPlural: Boolean? = null,

@Schema(
description =
"The argument name for the plural. " +
"If null, value will be guessed from the values provided in translations.",
)
val pluralArgName: String? = null,
)
13 changes: 11 additions & 2 deletions backend/data/src/main/kotlin/io/tolgee/formats/pluralFormsUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,22 @@ data class ConvertToIcuPluralResult<T>(
val argName: String,
)


data class PossibleConvertToIcuPluralResult<T>(
val convertedStrings: Map<T, String?>,
/** If null, it was not converted */
val argName: String?,
) {
val converted: Boolean = argName != null
}

/**
* Normalizes list of plurals. Uses provided argument name if any, otherwise it tries to find the most common one
*/
fun <T> normalizePlurals(
strings: Map<T, String?>,
pluralArgName: String? = null,
): Map<T, String?> {
): ConvertToIcuPluralResult<T> {
val invalidStrings = mutableListOf<String>()
val formResults =
strings.map {
Expand All @@ -238,7 +247,7 @@ fun <T> normalizePlurals(
throw StringIsNotPluralException(invalidStrings)
}

return pluralFormsToSameArgName(formResults, pluralArgName).convertedStrings
return pluralFormsToSameArgName(formResults, pluralArgName)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface KeyRepository : JpaRepository<Key, Long> {

@Query(
"""
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom)
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom, k.isPlural)
from Key k
left join k.keyMeta km
left join k.namespace ns
Expand Down Expand Up @@ -131,7 +131,7 @@ interface KeyRepository : JpaRepository<Key, Long> {

@Query(
"""
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom)
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom, k.isPlural)
from Key k
left join k.keyMeta km
left join k.namespace ns
Expand All @@ -149,7 +149,7 @@ interface KeyRepository : JpaRepository<Key, Long> {

@Query(
"""
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom)
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom, k.isPlural)
from Key k
left join k.keyMeta km
left join k.namespace ns
Expand Down Expand Up @@ -238,7 +238,7 @@ interface KeyRepository : JpaRepository<Key, Long> {

@Query(
"""
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom)
select new io.tolgee.dtos.queryResults.KeyView(k.id, k.name, ns.name, km.description, km.custom, k.isPlural)
from Key k
left join k.keyMeta km
left join k.namespace ns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.tolgee.model.translation.Translation
import io.tolgee.repository.KeyRepository
import io.tolgee.repository.LanguageRepository
import io.tolgee.service.bigMeta.BigMetaService
import io.tolgee.service.key.resolvableImport.ResolvableImporter
import io.tolgee.service.key.utils.KeyInfoProvider
import io.tolgee.service.key.utils.KeysImporter
import io.tolgee.service.translation.TranslationService
Expand Down Expand Up @@ -370,7 +371,7 @@ class KeyService(
projectEntity: Project,
): KeyImportResolvableResult {
val importer =
ResolvingKeyImporter(
ResolvableImporter(
applicationContext = applicationContext,
keysToImport = keys,
projectEntity = projectEntity,
Expand Down
Loading
Loading