diff --git a/api/Vigilance.api b/api/Vigilance.api index 1afa27d4..c129f263 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 @@ -651,6 +654,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 83522207..834a0398 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, lineSpacing = 10f).constrain { + UIWrappedText(data.attributesExt.localizedDescription(data.instance), lineSpacing = 10f).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