Skip to content

Commit

Permalink
feat: add custom internationalization provider support (#2)
Browse files Browse the repository at this point in the history
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).

EssentialGG#55
  • Loading branch information
My-Name-Is-Jeff authored Mar 20, 2024
2 parents 10c424e + 4c0a270 commit d8f8166
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 37 deletions.
12 changes: 12 additions & 0 deletions api/Vigilance.api
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public abstract class gg/essential/vigilance/Vigilant {
public fun <init> (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;)V
public fun <init> (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (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 <init> (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/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun addDependency (Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;)V
Expand All @@ -26,6 +28,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;
protected fun getMigrations ()Ljava/util/List;
public final fun getSortingBehavior ()Lgg/essential/vigilance/data/SortingBehavior;
public final fun gui ()Lgg/essential/vigilance/gui/SettingsGui;
Expand Down Expand Up @@ -667,6 +670,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;
}
Expand Down
32 changes: 22 additions & 10 deletions src/main/kotlin/gg/essential/vigilance/Vigilant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.migrate
import gg.essential.vigilance.impl.nightconfig.core.file.FileConfig
import java.awt.Color
Expand All @@ -18,12 +19,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<Vigilant>).memberProperties
Expand Down Expand Up @@ -272,16 +282,16 @@ abstract class Vigilant @JvmOverloads constructor(
fun getCategories(): List<Category> {
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())

Expand Down Expand Up @@ -353,15 +363,17 @@ abstract class Vigilant @JvmOverloads constructor(
}

private fun List<PropertyData>.splitBySubcategory(): List<CategoryItem> {
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<CategoryItem>()

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
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/gg/essential/vigilance/data/Categories.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<CategoryItem>, val description: String?) {
override fun toString(): String {
Expand Down Expand Up @@ -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(),
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/gg/essential/vigilance/data/Property.kt
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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 {
Expand Down
72 changes: 71 additions & 1 deletion src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -522,6 +523,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",
Expand Down Expand Up @@ -580,5 +621,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
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, shadowColor = VigilancePalette.getTextShadowLight()).constrain {
private val settingName by UIWrappedText(data.attributesExt.localizedName(data.instance), shadowColor = VigilancePalette.getTextShadowLight()).constrain {
width = 100.percent
textScale = GuiScaleOffsetConstraint(1f)
color = VigilancePalette.textHighlight.toConstraint()
} childOf textBoundingBox

init {
UIWrappedText(data.attributesExt.localizedDescription, shadowColor = VigilancePalette.getTextShadowLight(), lineSpacing = 10f).constrain {
UIWrappedText(data.attributesExt.localizedDescription(data.instance), shadowColor = VigilancePalette.getTextShadowLight(), lineSpacing = 10f).constrain {
y = SiblingConstraint() + 3.pixels
width = 100.percent
color = VigilancePalette.text.toConstraint()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = BasicState(placeholder.orEmpty().ifEmpty { "Activate" }).map { I18n.format(it) }
private var textState: State<String> = BasicState(placeholder.orEmpty().ifEmpty { "Activate" })
private var listener: () -> Unit = textState.onSetValue {
text.setText(textState.get())
}
Expand Down Expand Up @@ -67,7 +66,7 @@ class ButtonComponent(placeholder: String? = null, private val callback: () -> U
fun bindText(newTextState: State<String>) = apply {
listener()
textState = newTextState
text.bindText(textState.map { I18n.format(it) })
text.bindText(textState)

listener = textState.onSetValue {
text.setText(textState.get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) : 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 {
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/gg/essential/vigilance/i18n/I18nProvider.kt
Original file line number Diff line number Diff line change
@@ -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

}
Original file line number Diff line number Diff line change
@@ -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)
}
9 changes: 0 additions & 9 deletions src/main/kotlin/gg/essential/vigilance/impl/I18n.kt

This file was deleted.

2 changes: 2 additions & 0 deletions src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -252,6 +253,7 @@ internal operator fun <T> State<T>.setValue(obj: Any, property: KProperty<*>, va

internal fun <T> T.state() = BasicState(this)

internal fun PropertyData.translate(key: String) = this.instance.i18nProvider.translate(key)
internal fun <T> UIComponent.pollingState(initialValue: T? = null, getter: () -> T): State<T> {
val state = BasicState(initialValue ?: getter())
enableEffect(object : Effect() {
Expand Down

0 comments on commit d8f8166

Please sign in to comment.