From 57d61419dfa8afffcd2eb1dfcd752e37161d6568 Mon Sep 17 00:00:00 2001 From: DJtheRedstoner <52044242+DJtheRedstoner@users.noreply.github.com> Date: Thu, 14 Jul 2022 11:05:05 -0400 Subject: [PATCH] add custom internationalization provider support fixes addSubcategoryDescription requiring the localized subcategory name The subcategory comparator in SortingBehavior is still passed the localized version of names. This is not ideal, but changing this would require breaking changes (or introducing a new version of SortingBehavior). --- api/Vigilance.api | 12 ++++ .../kotlin/gg/essential/vigilance/Vigilant.kt | 32 ++++++--- .../gg/essential/vigilance/data/Categories.kt | 5 +- .../gg/essential/vigilance/data/Property.kt | 12 ++-- .../vigilance/example/ExampleConfig.kt | 72 ++++++++++++++++++- .../vigilance/gui/DataBackedSetting.kt | 4 +- .../vigilance/gui/SettingsTitleBar.kt | 3 +- .../vigilance/gui/settings/ButtonComponent.kt | 5 +- .../gui/settings/SelectorComponent.kt | 3 +- .../essential/vigilance/i18n/I18nProvider.kt | 20 ++++++ .../vigilance/i18n/PlatformI18nProvider.kt | 7 ++ .../gg/essential/vigilance/impl/I18n.kt | 9 --- .../essential/vigilance/utils/Extensions.kt | 3 + 13 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 src/main/kotlin/gg/essential/vigilance/i18n/I18nProvider.kt create mode 100644 src/main/kotlin/gg/essential/vigilance/i18n/PlatformI18nProvider.kt delete mode 100644 src/main/kotlin/gg/essential/vigilance/impl/I18n.kt diff --git a/api/Vigilance.api b/api/Vigilance.api index 3e3459f1..fd56cf1c 100644 --- a/api/Vigilance.api +++ b/api/Vigilance.api @@ -14,6 +14,8 @@ public abstract class gg/essential/vigilance/Vigilant { public fun (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;)V public fun (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;)V public synthetic fun (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;Lgg/essential/vigilance/i18n/I18nProvider;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;Lgg/essential/vigilance/i18n/I18nProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addDependency (Ljava/lang/String;Ljava/lang/String;)V public final fun addDependency (Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;)V public final fun addDependency (Lkotlin/reflect/KProperty;Lkotlin/reflect/KProperty;)V @@ -22,6 +24,7 @@ public abstract class gg/essential/vigilance/Vigilant { public final fun getCategories ()Ljava/util/List; public final fun getCategoryFromSearch (Ljava/lang/String;)Lgg/essential/vigilance/data/Category; public final fun getGuiTitle ()Ljava/lang/String; + public final fun getI18nProvider ()Lgg/essential/vigilance/i18n/I18nProvider; public final fun getSortingBehavior ()Lgg/essential/vigilance/data/SortingBehavior; public final fun gui ()Lgg/essential/vigilance/gui/SettingsGui; public final fun hiddenIf (Lkotlin/reflect/KProperty;Lkotlin/jvm/functions/Function0;)V @@ -650,6 +653,15 @@ public final class gg/essential/vigilance/gui/settings/TextComponent : gg/essent public fun closePopups (Z)V } +public abstract interface class gg/essential/vigilance/i18n/I18nProvider { + public abstract fun translate (Ljava/lang/String;)Ljava/lang/String; +} + +public final class gg/essential/vigilance/i18n/PlatformI18nProvider : gg/essential/vigilance/i18n/I18nProvider { + public static final field INSTANCE Lgg/essential/vigilance/i18n/PlatformI18nProvider; + public fun translate (Ljava/lang/String;)Ljava/lang/String; +} + public final class gg/essential/vigilance/utils/ExtensionsKt { public static final fun onLeftClick (Lgg/essential/elementa/UIComponent;Lkotlin/jvm/functions/Function2;)Lgg/essential/elementa/UIComponent; } diff --git a/src/main/kotlin/gg/essential/vigilance/Vigilant.kt b/src/main/kotlin/gg/essential/vigilance/Vigilant.kt index d4b8f13f..152ff623 100644 --- a/src/main/kotlin/gg/essential/vigilance/Vigilant.kt +++ b/src/main/kotlin/gg/essential/vigilance/Vigilant.kt @@ -3,7 +3,8 @@ package gg.essential.vigilance import gg.essential.universal.UChat import gg.essential.vigilance.data.* import gg.essential.vigilance.gui.SettingsGui -import gg.essential.vigilance.impl.I18n +import gg.essential.vigilance.i18n.I18nProvider +import gg.essential.vigilance.i18n.PlatformI18nProvider import gg.essential.vigilance.impl.nightconfig.core.file.FileConfig import java.awt.Color import java.io.File @@ -15,12 +16,21 @@ import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty import kotlin.reflect.jvm.javaField -abstract class Vigilant @JvmOverloads constructor( +abstract class Vigilant( file: File, val guiTitle: String = "Settings", private val propertyCollector: PropertyCollector = JVMAnnotationPropertyCollector(), - val sortingBehavior: SortingBehavior = SortingBehavior() + val sortingBehavior: SortingBehavior = SortingBehavior(), + val i18nProvider: I18nProvider = PlatformI18nProvider ) { + + @JvmOverloads constructor( + file: File, + guiTitle: String = "Settings", + propertyCollector: PropertyCollector = JVMAnnotationPropertyCollector(), + sortingBehavior: SortingBehavior = SortingBehavior() + ) : this(file, guiTitle, propertyCollector, sortingBehavior, PlatformI18nProvider) + /* TODO: Fix this in production private val miscData = (this::class as KClass).memberProperties @@ -196,16 +206,16 @@ abstract class Vigilant @JvmOverloads constructor( fun getCategories(): List { return propertyCollector.getProperties() .filter { !it.attributesExt.hidden } - .groupBy { it.attributesExt.localizedCategory to it.attributesExt.category } - .map { Category(it.key.first, it.value.splitBySubcategory(), categoryDescription[it.key.second]?.description?.let { desc -> I18n.format(desc) }) } + .groupBy { it.attributesExt.localizedCategory(this) to it.attributesExt.category } + .map { Category(it.key.first, it.value.splitBySubcategory(), categoryDescription[it.key.second]?.description?.let { desc -> i18nProvider.translate(desc) }) } .sortedWith(sortingBehavior.getCategoryComparator()) } fun getCategoryFromSearch(term: String): Category { val sorted = propertyCollector.getProperties() .filter { - !it.attributesExt.hidden && (it.attributesExt.localizedName.contains(term, ignoreCase = true) || it.attributesExt.localizedDescription - .contains(term, ignoreCase = true) || it.attributesExt.localizedSearchTags.any { str -> str.contains(term, ignoreCase = true) }) + !it.attributesExt.hidden && (it.attributesExt.localizedName(this).contains(term, ignoreCase = true) || it.attributesExt.localizedDescription(this) + .contains(term, ignoreCase = true) || it.attributesExt.localizedSearchTags(this).any { str -> str.contains(term, ignoreCase = true) }) } .sortedWith(sortingBehavior.getPropertyComparator()) @@ -275,15 +285,17 @@ abstract class Vigilant @JvmOverloads constructor( } private fun List.splitBySubcategory(): List { - val items = this.groupBy { it.attributesExt.localizedSubcategory }.entries.sortedWith(sortingBehavior.getSubcategoryComparator()) + val items = this.groupBy { it.attributesExt.localizedSubcategory(this@Vigilant) }.entries.sortedWith(sortingBehavior.getSubcategoryComparator()) val withDividers = mutableListOf() items.forEachIndexed { index, (subcategoryName, listOfProperties) -> - val subcategoryInfo = categoryDescription[listOfProperties[0].attributesExt.category]?.subcategoryDescriptions?.get(subcategoryName)?.let { I18n.format(it) } + val firstProperty = listOfProperties[0] + val subcategoryInfo = categoryDescription[firstProperty.attributesExt.category]?.subcategoryDescriptions + ?.get(firstProperty.attributesExt.subcategory)?.let { i18nProvider.translate(it) } if (index > 0 || subcategoryName.isNotBlank() || !subcategoryInfo.isNullOrBlank()) { withDividers.add(DividerItem(subcategoryName, subcategoryInfo)) } - withDividers.addAll(listOfProperties.sortedWith(sortingBehavior.getPropertyComparator()).map { PropertyItem(it, it.attributesExt.localizedSubcategory) }) + withDividers.addAll(listOfProperties.sortedWith(sortingBehavior.getPropertyComparator()).map { PropertyItem(it, it.attributesExt.localizedSubcategory(this@Vigilant)) }) } return withDividers diff --git a/src/main/kotlin/gg/essential/vigilance/data/Categories.kt b/src/main/kotlin/gg/essential/vigilance/data/Categories.kt index 1509e066..1d0dc65a 100644 --- a/src/main/kotlin/gg/essential/vigilance/data/Categories.kt +++ b/src/main/kotlin/gg/essential/vigilance/data/Categories.kt @@ -2,6 +2,7 @@ package gg.essential.vigilance.data import gg.essential.vigilance.gui.* import gg.essential.vigilance.gui.settings.* +import gg.essential.vigilance.utils.translate class Category(val name: String, val items: List, val description: String?) { override fun toString(): String { @@ -42,7 +43,7 @@ class PropertyItem(val data: PropertyData, val subcategory: String) : CategoryIt data.attributesExt.max, data.attributesExt.increment ) - PropertyType.SELECTOR -> SelectorComponent(data.getValue(), data.attributesExt.options.toList()) + PropertyType.SELECTOR -> SelectorComponent(data.getValue(), data.attributesExt.options.toList().map(data::translate)) PropertyType.COLOR -> ColorComponent(data.getValue(), data.attributesExt.allowAlpha) PropertyType.TEXT -> TextComponent( data.getValue(), @@ -56,7 +57,7 @@ class PropertyItem(val data: PropertyData, val subcategory: String) : CategoryIt wrap = true, protected = false ) - PropertyType.BUTTON -> ButtonComponent(data.attributesExt.placeholder, data) + PropertyType.BUTTON -> ButtonComponent(data.translate(data.attributesExt.placeholder), data) PropertyType.CUSTOM -> { val propertyInfoClass = data.attributesExt.customPropertyInfo propertyInfoClass diff --git a/src/main/kotlin/gg/essential/vigilance/data/Property.kt b/src/main/kotlin/gg/essential/vigilance/data/Property.kt index 5525f2c7..1ebdafbb 100644 --- a/src/main/kotlin/gg/essential/vigilance/data/Property.kt +++ b/src/main/kotlin/gg/essential/vigilance/data/Property.kt @@ -1,6 +1,6 @@ package gg.essential.vigilance.data -import gg.essential.vigilance.impl.I18n +import gg.essential.vigilance.Vigilant import java.util.* import kotlin.reflect.KClass @@ -300,15 +300,15 @@ class PropertyAttributesExt( ) : this(type, name, category, subcategory, description, min, max, minF, maxF, decimalPlaces, increment, options, allowAlpha, placeholder, protected, triggerActionOnInitialization, hidden, searchTags, i18nName, i18nCategory, i18nSubcategory) - internal val localizedName get() = I18n.format(i18nName) + internal fun localizedName(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nName) - internal val localizedCategory get() = I18n.format(i18nCategory) + internal fun localizedCategory(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nCategory) - internal val localizedSubcategory get() = I18n.format(i18nSubcategory) + internal fun localizedSubcategory(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nSubcategory) - internal val localizedDescription get() = I18n.format(description) + internal fun localizedDescription(vigilant: Vigilant) = vigilant.i18nProvider.translate(description) - internal val localizedSearchTags get() = searchTags.map { I18n.format(it) } + internal fun localizedSearchTags(vigilant: Vigilant) = searchTags.map { vigilant.i18nProvider.translate(it) } companion object { fun fromPropertyAnnotation(property: Property): PropertyAttributesExt { diff --git a/src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt b/src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt index c6799044..1c1ce956 100644 --- a/src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt +++ b/src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt @@ -6,6 +6,7 @@ import gg.essential.universal.UChat import gg.essential.vigilance.Vigilant import gg.essential.vigilance.data.Property import gg.essential.vigilance.data.PropertyType +import gg.essential.vigilance.i18n.I18nProvider import java.awt.Color import java.io.File import kotlin.math.PI @@ -15,7 +16,7 @@ import kotlin.math.PI * as well as a visual demonstration of each option. Also demos some * aspects such as fields with different initial values. */ -object ExampleConfig : Vigilant(File("./config/example.toml")) { +object ExampleConfig : Vigilant(File("./config/example.toml"), i18nProvider = ExampleI18nProvider) { @Property( type = PropertyType.CHECKBOX, name = "Checkbox", @@ -494,6 +495,46 @@ object ExampleConfig : Vigilant(File("./config/example.toml")) { ) var linuxOnlyProperty = false + @Property( + type = PropertyType.SWITCH, + name = "config.switch", + description = "config.switch.description", + category = "Property Deep-Dive", + subcategory = "config.subcategory.localized", + searchTags = ["config.searchtag.i18n"] + ) + var localizedSwitch = false + + @Property( + type = PropertyType.BUTTON, + name = "config.button", + description = "config.button.description", + placeholder = "config.button.placeholder", + category = "Property Deep-Dive", + subcategory = "config.subcategory.localized", + searchTags = ["config.searchtag.i18n"] + ) + fun localizedButton() { + + } + + @Property( + type = PropertyType.SELECTOR, + name = "config.selector", + description = "config.selector.description", + options = [ + "config.selector.1", + "config.selector.2", + "config.selector.3", + "config.selector.4", + "config.selector.5" + ], + category = "Property Deep-Dive", + subcategory = "config.subcategory.localized", + searchTags = ["config.searchtag.i18n"] + ) + var localizedSelector = 0 + @Property( type = PropertyType.SWITCH, name = "This is a switch property with a very long name. It is recommended to use the description for lengthy property text, however this is still supported", @@ -550,5 +591,34 @@ object ExampleConfig : Vigilant(File("./config/example.toml")) { "Buttons", "Buttons are a great way for the user to run an action. Buttons don't have any associated state, and as such their annotation target has to be a method." ) + + setSubcategoryDescription( + "Property Deep-Dive", + "config.subcategory.localized", + "config.subcategory.localized.description" + ) + } + + object ExampleI18nProvider : I18nProvider { + override fun translate(key: String): String = + when(key) { + "config.subcategory.localized" -> "Localized" + "config.subcategory.localized.description" -> "Vigilance has (some) localization support!" + "config.switch" -> "Localized switch" + "config.switch.description" -> "Localized switch description" + "config.button" -> "Localized Button" + "config.button.description" -> "Localized button description" + "config.button.placeholder" -> "Click me!" + "config.selector" -> "Localized selector" + "config.selector.description" -> "Localized selector description" + "config.selector.1" -> "Localized option 1" + "config.selector.2" -> "Localized option 2" + "config.selector.3" -> "Localized option 3" + "config.selector.4" -> "Localized option 4" + "config.selector.5" -> "Localized option 5" + "config.searchtag.i18n" -> "internationalization" + else -> key + } + } } \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/vigilance/gui/DataBackedSetting.kt b/src/main/kotlin/gg/essential/vigilance/gui/DataBackedSetting.kt index dd04b166..77f9b74b 100644 --- a/src/main/kotlin/gg/essential/vigilance/gui/DataBackedSetting.kt +++ b/src/main/kotlin/gg/essential/vigilance/gui/DataBackedSetting.kt @@ -32,14 +32,14 @@ class DataBackedSetting(internal val data: PropertyData, internal val component: height = ChildBasedSizeConstraint(3f) + INNER_PADDING.pixels } childOf boundingBox - private val settingName by UIWrappedText(data.attributesExt.localizedName).constrain { + private val settingName by UIWrappedText(data.attributesExt.localizedName(data.instance)).constrain { width = 100.percent textScale = GuiScaleOffsetConstraint(1f) color = VigilancePalette.textHighlight.toConstraint() } childOf textBoundingBox init { - UIWrappedText(data.attributesExt.localizedDescription).constrain { + UIWrappedText(data.attributesExt.localizedDescription(data.instance)).constrain { y = SiblingConstraint() + 3.pixels width = 100.percent color = VigilancePalette.text.toConstraint() diff --git a/src/main/kotlin/gg/essential/vigilance/gui/SettingsTitleBar.kt b/src/main/kotlin/gg/essential/vigilance/gui/SettingsTitleBar.kt index 112329e0..2606a474 100644 --- a/src/main/kotlin/gg/essential/vigilance/gui/SettingsTitleBar.kt +++ b/src/main/kotlin/gg/essential/vigilance/gui/SettingsTitleBar.kt @@ -8,7 +8,6 @@ import gg.essential.elementa.constraints.CenterConstraint import gg.essential.elementa.constraints.SiblingConstraint import gg.essential.elementa.dsl.* import gg.essential.vigilance.Vigilant -import gg.essential.vigilance.impl.I18n class SettingsTitleBar(private val gui: SettingsGui, private val config: Vigilant, window: Window) : UIContainer() { @@ -31,7 +30,7 @@ class SettingsTitleBar(private val gui: SettingsGui, private val config: Vigilan height = 100.percent } childOf this - private val titleText by UIText(I18n.format(config.guiTitle)).constrain { + private val titleText by UIText(config.i18nProvider.translate(config.guiTitle)).constrain { x = 10.pixels y = CenterConstraint() } childOf contentContainer diff --git a/src/main/kotlin/gg/essential/vigilance/gui/settings/ButtonComponent.kt b/src/main/kotlin/gg/essential/vigilance/gui/settings/ButtonComponent.kt index fbce94ec..1cf65136 100644 --- a/src/main/kotlin/gg/essential/vigilance/gui/settings/ButtonComponent.kt +++ b/src/main/kotlin/gg/essential/vigilance/gui/settings/ButtonComponent.kt @@ -15,12 +15,11 @@ import gg.essential.vigilance.data.CallablePropertyValue import gg.essential.vigilance.data.PropertyData import gg.essential.vigilance.gui.ExpandingClickEffect import gg.essential.vigilance.gui.VigilancePalette -import gg.essential.vigilance.impl.I18n import gg.essential.vigilance.utils.onLeftClick class ButtonComponent(placeholder: String? = null, private val callback: () -> Unit) : SettingComponent() { - private var textState: State = BasicState(placeholder.orEmpty().ifEmpty { "Activate" }).map { I18n.format(it) } + private var textState: State = BasicState(placeholder.orEmpty().ifEmpty { "Activate" }) private var listener: () -> Unit = textState.onSetValue { text.setText(textState.get()) } @@ -67,7 +66,7 @@ class ButtonComponent(placeholder: String? = null, private val callback: () -> U fun bindText(newTextState: State) = apply { listener() textState = newTextState - text.bindText(textState.map { I18n.format(it) }) + text.bindText(textState) listener = textState.onSetValue { text.setText(textState.get()) diff --git a/src/main/kotlin/gg/essential/vigilance/gui/settings/SelectorComponent.kt b/src/main/kotlin/gg/essential/vigilance/gui/settings/SelectorComponent.kt index 52d86d59..bd1b6eb8 100644 --- a/src/main/kotlin/gg/essential/vigilance/gui/settings/SelectorComponent.kt +++ b/src/main/kotlin/gg/essential/vigilance/gui/settings/SelectorComponent.kt @@ -2,11 +2,10 @@ package gg.essential.vigilance.gui.settings import gg.essential.elementa.constraints.ChildBasedSizeConstraint import gg.essential.elementa.dsl.* -import gg.essential.vigilance.impl.I18n class SelectorComponent(initialSelection: Int, options: List) : SettingComponent() { - internal val dropDown by DropDownComponent(initialSelection, options.map { I18n.format(it) }) childOf this + internal val dropDown by DropDownComponent(initialSelection, options) childOf this init { constrain { diff --git a/src/main/kotlin/gg/essential/vigilance/i18n/I18nProvider.kt b/src/main/kotlin/gg/essential/vigilance/i18n/I18nProvider.kt new file mode 100644 index 00000000..c93a8cba --- /dev/null +++ b/src/main/kotlin/gg/essential/vigilance/i18n/I18nProvider.kt @@ -0,0 +1,20 @@ +package gg.essential.vigilance.i18n + +import gg.essential.vigilance.Vigilant + +/** + * An interface that can be implemented to allow for the use of custom internationalization + * systems. To use a custom provider, pass it to the `i18nProvider` argument of the + * [Vigilant] constructor. The default provider, [PlatformI18nProvider], uses Minecraft's + * internationalization system. + */ +fun interface I18nProvider { + + /** + * Localizes a key + * @param key the localization key + * @return the localized string + */ + fun translate(key: String): String + +} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/vigilance/i18n/PlatformI18nProvider.kt b/src/main/kotlin/gg/essential/vigilance/i18n/PlatformI18nProvider.kt new file mode 100644 index 00000000..79546a70 --- /dev/null +++ b/src/main/kotlin/gg/essential/vigilance/i18n/PlatformI18nProvider.kt @@ -0,0 +1,7 @@ +package gg.essential.vigilance.i18n + +import gg.essential.vigilance.impl.Platform.Companion.platform + +object PlatformI18nProvider: I18nProvider { + override fun translate(key: String): String = platform.i18n(key) +} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/vigilance/impl/I18n.kt b/src/main/kotlin/gg/essential/vigilance/impl/I18n.kt deleted file mode 100644 index 34af541f..00000000 --- a/src/main/kotlin/gg/essential/vigilance/impl/I18n.kt +++ /dev/null @@ -1,9 +0,0 @@ -package gg.essential.vigilance.impl - -import gg.essential.vigilance.impl.Platform.Companion.platform -import org.jetbrains.annotations.ApiStatus - -@ApiStatus.Internal -object I18n { - fun format(key: String): String = platform.i18n(key) -} \ No newline at end of file diff --git a/src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt b/src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt index b0c174fa..51eb8334 100644 --- a/src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt +++ b/src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt @@ -15,6 +15,7 @@ import gg.essential.elementa.state.State import gg.essential.elementa.utils.withAlpha import gg.essential.universal.UMouse import gg.essential.universal.UResolution +import gg.essential.vigilance.data.PropertyData import gg.essential.vigilance.gui.VigilancePalette import java.awt.Color import kotlin.reflect.KProperty @@ -247,3 +248,5 @@ internal operator fun State.getValue(obj: Any, property: KProperty<*>): T internal operator fun State.setValue(obj: Any, property: KProperty<*>, value: T) = set(value) internal fun T.state() = BasicState(this) + +internal fun PropertyData.translate(key: String) = this.instance.i18nProvider.translate(key) \ No newline at end of file