Skip to content

Commit d8f8166

Browse files
feat: add custom internationalization provider support (#2)
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
2 parents 10c424e + 4c0a270 commit d8f8166

File tree

13 files changed

+149
-37
lines changed

13 files changed

+149
-37
lines changed

Diff for: api/Vigilance.api

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public abstract class gg/essential/vigilance/Vigilant {
1414
public fun <init> (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;)V
1515
public fun <init> (Ljava/io/File;Ljava/lang/String;Lgg/essential/vigilance/data/PropertyCollector;Lgg/essential/vigilance/data/SortingBehavior;)V
1616
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
17+
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
18+
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
1719
public final fun addDependency (Ljava/lang/String;Ljava/lang/String;)V
1820
public final fun addDependency (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
1921
public final fun addDependency (Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;)V
@@ -26,6 +28,7 @@ public abstract class gg/essential/vigilance/Vigilant {
2628
public final fun getCategories ()Ljava/util/List;
2729
public final fun getCategoryFromSearch (Ljava/lang/String;)Lgg/essential/vigilance/data/Category;
2830
public final fun getGuiTitle ()Ljava/lang/String;
31+
public final fun getI18nProvider ()Lgg/essential/vigilance/i18n/I18nProvider;
2932
protected fun getMigrations ()Ljava/util/List;
3033
public final fun getSortingBehavior ()Lgg/essential/vigilance/data/SortingBehavior;
3134
public final fun gui ()Lgg/essential/vigilance/gui/SettingsGui;
@@ -667,6 +670,15 @@ public final class gg/essential/vigilance/gui/settings/TextComponent : gg/essent
667670
public fun closePopups (Z)V
668671
}
669672

673+
public abstract interface class gg/essential/vigilance/i18n/I18nProvider {
674+
public abstract fun translate (Ljava/lang/String;)Ljava/lang/String;
675+
}
676+
677+
public final class gg/essential/vigilance/i18n/PlatformI18nProvider : gg/essential/vigilance/i18n/I18nProvider {
678+
public static final field INSTANCE Lgg/essential/vigilance/i18n/PlatformI18nProvider;
679+
public fun translate (Ljava/lang/String;)Ljava/lang/String;
680+
}
681+
670682
public final class gg/essential/vigilance/utils/ExtensionsKt {
671683
public static final fun onLeftClick (Lgg/essential/elementa/UIComponent;Lkotlin/jvm/functions/Function2;)Lgg/essential/elementa/UIComponent;
672684
}

Diff for: src/main/kotlin/gg/essential/vigilance/Vigilant.kt

+22-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package gg.essential.vigilance
33
import gg.essential.universal.UChat
44
import gg.essential.vigilance.data.*
55
import gg.essential.vigilance.gui.SettingsGui
6-
import gg.essential.vigilance.impl.I18n
6+
import gg.essential.vigilance.i18n.I18nProvider
7+
import gg.essential.vigilance.i18n.PlatformI18nProvider
78
import gg.essential.vigilance.impl.migrate
89
import gg.essential.vigilance.impl.nightconfig.core.file.FileConfig
910
import java.awt.Color
@@ -18,12 +19,21 @@ import kotlin.reflect.KMutableProperty0
1819
import kotlin.reflect.KProperty
1920
import kotlin.reflect.jvm.javaField
2021

21-
abstract class Vigilant @JvmOverloads constructor(
22+
abstract class Vigilant(
2223
file: File,
2324
val guiTitle: String = "Settings",
2425
private val propertyCollector: PropertyCollector = JVMAnnotationPropertyCollector(),
25-
val sortingBehavior: SortingBehavior = SortingBehavior()
26+
val sortingBehavior: SortingBehavior = SortingBehavior(),
27+
val i18nProvider: I18nProvider = PlatformI18nProvider
2628
) {
29+
30+
@JvmOverloads constructor(
31+
file: File,
32+
guiTitle: String = "Settings",
33+
propertyCollector: PropertyCollector = JVMAnnotationPropertyCollector(),
34+
sortingBehavior: SortingBehavior = SortingBehavior()
35+
) : this(file, guiTitle, propertyCollector, sortingBehavior, PlatformI18nProvider)
36+
2737
/*
2838
TODO: Fix this in production
2939
private val miscData = (this::class as KClass<Vigilant>).memberProperties
@@ -272,16 +282,16 @@ abstract class Vigilant @JvmOverloads constructor(
272282
fun getCategories(): List<Category> {
273283
return propertyCollector.getProperties()
274284
.filter { !it.attributesExt.hidden }
275-
.groupBy { it.attributesExt.localizedCategory to it.attributesExt.category }
276-
.map { Category(it.key.first, it.value.splitBySubcategory(), categoryDescription[it.key.second]?.description?.let { desc -> I18n.format(desc) }) }
285+
.groupBy { it.attributesExt.localizedCategory(this) to it.attributesExt.category }
286+
.map { Category(it.key.first, it.value.splitBySubcategory(), categoryDescription[it.key.second]?.description?.let { desc -> i18nProvider.translate(desc) }) }
277287
.sortedWith(sortingBehavior.getCategoryComparator())
278288
}
279289

280290
fun getCategoryFromSearch(term: String): Category {
281291
val sorted = propertyCollector.getProperties()
282292
.filter {
283-
!it.attributesExt.hidden && (it.attributesExt.localizedName.contains(term, ignoreCase = true) || it.attributesExt.localizedDescription
284-
.contains(term, ignoreCase = true) || it.attributesExt.localizedSearchTags.any { str -> str.contains(term, ignoreCase = true) })
293+
!it.attributesExt.hidden && (it.attributesExt.localizedName(this).contains(term, ignoreCase = true) || it.attributesExt.localizedDescription(this)
294+
.contains(term, ignoreCase = true) || it.attributesExt.localizedSearchTags(this).any { str -> str.contains(term, ignoreCase = true) })
285295
}
286296
.sortedWith(sortingBehavior.getPropertyComparator())
287297

@@ -353,15 +363,17 @@ abstract class Vigilant @JvmOverloads constructor(
353363
}
354364

355365
private fun List<PropertyData>.splitBySubcategory(): List<CategoryItem> {
356-
val items = this.groupBy { it.attributesExt.localizedSubcategory }.entries.sortedWith(sortingBehavior.getSubcategoryComparator())
366+
val items = this.groupBy { it.attributesExt.localizedSubcategory(this@Vigilant) }.entries.sortedWith(sortingBehavior.getSubcategoryComparator())
357367
val withDividers = mutableListOf<CategoryItem>()
358368

359369
items.forEachIndexed { index, (subcategoryName, listOfProperties) ->
360-
val subcategoryInfo = categoryDescription[listOfProperties[0].attributesExt.category]?.subcategoryDescriptions?.get(subcategoryName)?.let { I18n.format(it) }
370+
val firstProperty = listOfProperties[0]
371+
val subcategoryInfo = categoryDescription[firstProperty.attributesExt.category]?.subcategoryDescriptions
372+
?.get(firstProperty.attributesExt.subcategory)?.let { i18nProvider.translate(it) }
361373
if (index > 0 || subcategoryName.isNotBlank() || !subcategoryInfo.isNullOrBlank()) {
362374
withDividers.add(DividerItem(subcategoryName, subcategoryInfo))
363375
}
364-
withDividers.addAll(listOfProperties.sortedWith(sortingBehavior.getPropertyComparator()).map { PropertyItem(it, it.attributesExt.localizedSubcategory) })
376+
withDividers.addAll(listOfProperties.sortedWith(sortingBehavior.getPropertyComparator()).map { PropertyItem(it, it.attributesExt.localizedSubcategory(this@Vigilant)) })
365377
}
366378

367379
return withDividers

Diff for: src/main/kotlin/gg/essential/vigilance/data/Categories.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gg.essential.vigilance.data
22

33
import gg.essential.vigilance.gui.*
44
import gg.essential.vigilance.gui.settings.*
5+
import gg.essential.vigilance.utils.translate
56

67
class Category(val name: String, val items: List<CategoryItem>, val description: String?) {
78
override fun toString(): String {
@@ -42,7 +43,7 @@ class PropertyItem(val data: PropertyData, val subcategory: String) : CategoryIt
4243
data.attributesExt.max,
4344
data.attributesExt.increment
4445
)
45-
PropertyType.SELECTOR -> SelectorComponent(data.getValue(), data.attributesExt.options.toList())
46+
PropertyType.SELECTOR -> SelectorComponent(data.getValue(), data.attributesExt.options.toList().map(data::translate))
4647
PropertyType.COLOR -> ColorComponent(data.getValue(), data.attributesExt.allowAlpha)
4748
PropertyType.TEXT -> TextComponent(
4849
data.getValue(),
@@ -56,7 +57,7 @@ class PropertyItem(val data: PropertyData, val subcategory: String) : CategoryIt
5657
wrap = true,
5758
protected = false
5859
)
59-
PropertyType.BUTTON -> ButtonComponent(data.attributesExt.placeholder, data)
60+
PropertyType.BUTTON -> ButtonComponent(data.translate(data.attributesExt.placeholder), data)
6061
PropertyType.CUSTOM -> {
6162
val propertyInfoClass = data.attributesExt.customPropertyInfo
6263
propertyInfoClass

Diff for: src/main/kotlin/gg/essential/vigilance/data/Property.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package gg.essential.vigilance.data
22

3-
import gg.essential.vigilance.impl.I18n
3+
import gg.essential.vigilance.Vigilant
44
import java.util.*
55
import kotlin.reflect.KClass
66

@@ -300,15 +300,15 @@ class PropertyAttributesExt(
300300
) : this(type, name, category, subcategory, description, min, max, minF, maxF, decimalPlaces, increment, options, allowAlpha, placeholder, protected, triggerActionOnInitialization, hidden, searchTags, i18nName, i18nCategory, i18nSubcategory)
301301

302302

303-
internal val localizedName get() = I18n.format(i18nName)
303+
internal fun localizedName(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nName)
304304

305-
internal val localizedCategory get() = I18n.format(i18nCategory)
305+
internal fun localizedCategory(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nCategory)
306306

307-
internal val localizedSubcategory get() = I18n.format(i18nSubcategory)
307+
internal fun localizedSubcategory(vigilant: Vigilant) = vigilant.i18nProvider.translate(i18nSubcategory)
308308

309-
internal val localizedDescription get() = I18n.format(description)
309+
internal fun localizedDescription(vigilant: Vigilant) = vigilant.i18nProvider.translate(description)
310310

311-
internal val localizedSearchTags get() = searchTags.map { I18n.format(it) }
311+
internal fun localizedSearchTags(vigilant: Vigilant) = searchTags.map { vigilant.i18nProvider.translate(it) }
312312

313313
companion object {
314314
fun fromPropertyAnnotation(property: Property): PropertyAttributesExt {

Diff for: src/main/kotlin/gg/essential/vigilance/example/ExampleConfig.kt

+71-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import gg.essential.universal.UChat
66
import gg.essential.vigilance.Vigilant
77
import gg.essential.vigilance.data.Property
88
import gg.essential.vigilance.data.PropertyType
9+
import gg.essential.vigilance.i18n.I18nProvider
910
import java.awt.Color
1011
import java.io.File
1112
import kotlin.math.PI
@@ -15,7 +16,7 @@ import kotlin.math.PI
1516
* as well as a visual demonstration of each option. Also demos some
1617
* aspects such as fields with different initial values.
1718
*/
18-
object ExampleConfig : Vigilant(File("./config/example.toml")) {
19+
object ExampleConfig : Vigilant(File("./config/example.toml"), i18nProvider = ExampleI18nProvider) {
1920
@Property(
2021
type = PropertyType.CHECKBOX,
2122
name = "Checkbox",
@@ -522,6 +523,46 @@ object ExampleConfig : Vigilant(File("./config/example.toml")) {
522523
)
523524
var linuxOnlyProperty = false
524525

526+
@Property(
527+
type = PropertyType.SWITCH,
528+
name = "config.switch",
529+
description = "config.switch.description",
530+
category = "Property Deep-Dive",
531+
subcategory = "config.subcategory.localized",
532+
searchTags = ["config.searchtag.i18n"]
533+
)
534+
var localizedSwitch = false
535+
536+
@Property(
537+
type = PropertyType.BUTTON,
538+
name = "config.button",
539+
description = "config.button.description",
540+
placeholder = "config.button.placeholder",
541+
category = "Property Deep-Dive",
542+
subcategory = "config.subcategory.localized",
543+
searchTags = ["config.searchtag.i18n"]
544+
)
545+
fun localizedButton() {
546+
547+
}
548+
549+
@Property(
550+
type = PropertyType.SELECTOR,
551+
name = "config.selector",
552+
description = "config.selector.description",
553+
options = [
554+
"config.selector.1",
555+
"config.selector.2",
556+
"config.selector.3",
557+
"config.selector.4",
558+
"config.selector.5"
559+
],
560+
category = "Property Deep-Dive",
561+
subcategory = "config.subcategory.localized",
562+
searchTags = ["config.searchtag.i18n"]
563+
)
564+
var localizedSelector = 0
565+
525566
@Property(
526567
type = PropertyType.SWITCH,
527568
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",
@@ -580,5 +621,34 @@ object ExampleConfig : Vigilant(File("./config/example.toml")) {
580621
"Buttons",
581622
"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."
582623
)
624+
625+
setSubcategoryDescription(
626+
"Property Deep-Dive",
627+
"config.subcategory.localized",
628+
"config.subcategory.localized.description"
629+
)
630+
}
631+
632+
object ExampleI18nProvider : I18nProvider {
633+
override fun translate(key: String): String =
634+
when(key) {
635+
"config.subcategory.localized" -> "Localized"
636+
"config.subcategory.localized.description" -> "Vigilance has (some) localization support!"
637+
"config.switch" -> "Localized switch"
638+
"config.switch.description" -> "Localized switch description"
639+
"config.button" -> "Localized Button"
640+
"config.button.description" -> "Localized button description"
641+
"config.button.placeholder" -> "Click me!"
642+
"config.selector" -> "Localized selector"
643+
"config.selector.description" -> "Localized selector description"
644+
"config.selector.1" -> "Localized option 1"
645+
"config.selector.2" -> "Localized option 2"
646+
"config.selector.3" -> "Localized option 3"
647+
"config.selector.4" -> "Localized option 4"
648+
"config.selector.5" -> "Localized option 5"
649+
"config.searchtag.i18n" -> "internationalization"
650+
else -> key
651+
}
652+
583653
}
584654
}

Diff for: src/main/kotlin/gg/essential/vigilance/gui/DataBackedSetting.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ class DataBackedSetting(internal val data: PropertyData, internal val component:
3232
height = ChildBasedSizeConstraint(3f) + INNER_PADDING.pixels
3333
} childOf boundingBox
3434

35-
private val settingName by UIWrappedText(data.attributesExt.localizedName, shadowColor = VigilancePalette.getTextShadowLight()).constrain {
35+
private val settingName by UIWrappedText(data.attributesExt.localizedName(data.instance), shadowColor = VigilancePalette.getTextShadowLight()).constrain {
3636
width = 100.percent
3737
textScale = GuiScaleOffsetConstraint(1f)
3838
color = VigilancePalette.textHighlight.toConstraint()
3939
} childOf textBoundingBox
4040

4141
init {
42-
UIWrappedText(data.attributesExt.localizedDescription, shadowColor = VigilancePalette.getTextShadowLight(), lineSpacing = 10f).constrain {
42+
UIWrappedText(data.attributesExt.localizedDescription(data.instance), shadowColor = VigilancePalette.getTextShadowLight(), lineSpacing = 10f).constrain {
4343
y = SiblingConstraint() + 3.pixels
4444
width = 100.percent
4545
color = VigilancePalette.text.toConstraint()

Diff for: src/main/kotlin/gg/essential/vigilance/gui/SettingsTitleBar.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import gg.essential.elementa.constraints.CenterConstraint
88
import gg.essential.elementa.constraints.SiblingConstraint
99
import gg.essential.elementa.dsl.*
1010
import gg.essential.vigilance.Vigilant
11-
import gg.essential.vigilance.impl.I18n
1211

1312
class SettingsTitleBar(private val gui: SettingsGui, private val config: Vigilant, window: Window) :
1413
UIContainer() {
@@ -31,7 +30,7 @@ class SettingsTitleBar(private val gui: SettingsGui, private val config: Vigilan
3130
height = 100.percent
3231
} childOf this
3332

34-
private val titleText by UIText(I18n.format(config.guiTitle)).constrain {
33+
private val titleText by UIText(config.i18nProvider.translate(config.guiTitle)).constrain {
3534
x = 10.pixels
3635
y = CenterConstraint()
3736
} childOf contentContainer

Diff for: src/main/kotlin/gg/essential/vigilance/gui/settings/ButtonComponent.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ import gg.essential.vigilance.data.CallablePropertyValue
1515
import gg.essential.vigilance.data.PropertyData
1616
import gg.essential.vigilance.gui.ExpandingClickEffect
1717
import gg.essential.vigilance.gui.VigilancePalette
18-
import gg.essential.vigilance.impl.I18n
1918
import gg.essential.vigilance.utils.onLeftClick
2019

2120
class ButtonComponent(placeholder: String? = null, private val callback: () -> Unit) : SettingComponent() {
2221

23-
private var textState: State<String> = BasicState(placeholder.orEmpty().ifEmpty { "Activate" }).map { I18n.format(it) }
22+
private var textState: State<String> = BasicState(placeholder.orEmpty().ifEmpty { "Activate" })
2423
private var listener: () -> Unit = textState.onSetValue {
2524
text.setText(textState.get())
2625
}
@@ -67,7 +66,7 @@ class ButtonComponent(placeholder: String? = null, private val callback: () -> U
6766
fun bindText(newTextState: State<String>) = apply {
6867
listener()
6968
textState = newTextState
70-
text.bindText(textState.map { I18n.format(it) })
69+
text.bindText(textState)
7170

7271
listener = textState.onSetValue {
7372
text.setText(textState.get())

Diff for: src/main/kotlin/gg/essential/vigilance/gui/settings/SelectorComponent.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ package gg.essential.vigilance.gui.settings
22

33
import gg.essential.elementa.constraints.ChildBasedSizeConstraint
44
import gg.essential.elementa.dsl.*
5-
import gg.essential.vigilance.impl.I18n
65

76
class SelectorComponent(initialSelection: Int, options: List<String>) : SettingComponent() {
87

9-
internal val dropDown by DropDownComponent(initialSelection, options.map { I18n.format(it) }) childOf this
8+
internal val dropDown by DropDownComponent(initialSelection, options) childOf this
109

1110
init {
1211
constrain {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package gg.essential.vigilance.i18n
2+
3+
import gg.essential.vigilance.Vigilant
4+
5+
/**
6+
* An interface that can be implemented to allow for the use of custom internationalization
7+
* systems. To use a custom provider, pass it to the `i18nProvider` argument of the
8+
* [Vigilant] constructor. The default provider, [PlatformI18nProvider], uses Minecraft's
9+
* internationalization system.
10+
*/
11+
fun interface I18nProvider {
12+
13+
/**
14+
* Localizes a key
15+
* @param key the localization key
16+
* @return the localized string
17+
*/
18+
fun translate(key: String): String
19+
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package gg.essential.vigilance.i18n
2+
3+
import gg.essential.vigilance.impl.Platform.Companion.platform
4+
5+
object PlatformI18nProvider: I18nProvider {
6+
override fun translate(key: String): String = platform.i18n(key)
7+
}

Diff for: src/main/kotlin/gg/essential/vigilance/impl/I18n.kt

-9
This file was deleted.

Diff for: src/main/kotlin/gg/essential/vigilance/utils/Extensions.kt

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import gg.essential.elementa.state.State
1515
import gg.essential.elementa.utils.withAlpha
1616
import gg.essential.universal.UMouse
1717
import gg.essential.universal.UResolution
18+
import gg.essential.vigilance.data.PropertyData
1819
import gg.essential.vigilance.gui.VigilancePalette
1920
import java.awt.Color
2021
import kotlin.reflect.KProperty
@@ -252,6 +253,7 @@ internal operator fun <T> State<T>.setValue(obj: Any, property: KProperty<*>, va
252253

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

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

0 commit comments

Comments
 (0)