Skip to content

Commit 3f24f23

Browse files
committed
[JEWEL-876] Improving string resources for standalone
- Implemented 'AbstractDynamicBundleTest', which is a simplified version of the IDE message loader - Updated 'IntUiMessageResourceResolver' to use the new bundle string loader
1 parent c4acc5f commit 3f24f23

File tree

13 files changed

+491
-16
lines changed

13 files changed

+491
-16
lines changed

platform/jewel/int-ui/int-ui-standalone-tests/BUILD.bazel

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### auto-generated section `build intellij.platform.jewel.intUi.standalone.tests` start
22
load("//build:compiler-options.bzl", "create_kotlinc_options")
3-
load("@rules_jvm//:jvm.bzl", "jvm_library")
3+
load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup")
44

55
create_kotlinc_options(
66
name = "custom_jewel-intUi-standalone-tests",
@@ -14,6 +14,23 @@ create_kotlinc_options(
1414
x_explicit_api_mode = "strict"
1515
)
1616

17+
resourcegroup(
18+
name = "jewel-intUi-standalone-tests_resources",
19+
srcs = glob(["src/test/resources/**/*"]),
20+
strip_prefix = "src/test/resources"
21+
)
22+
23+
jvm_library(
24+
name = "jewel-intUi-standalone-tests",
25+
visibility = ["//visibility:public"],
26+
srcs = glob([], allow_empty = True),
27+
resources = [":jewel-intUi-standalone-tests_resources"],
28+
runtime_deps = [
29+
"@lib//:kotlin-stdlib",
30+
"//platform/jewel/int-ui/int-ui-standalone:jewel-intUi-standalone",
31+
]
32+
)
33+
1734
jvm_library(
1835
name = "jewel-intUi-standalone-tests_test_lib",
1936
module_name = "intellij.platform.jewel.intUi.standalone.tests",
@@ -25,7 +42,8 @@ jvm_library(
2542
"@lib//:kotlin-test",
2643
"@lib//:junit5",
2744
"//platform/jewel/int-ui/int-ui-standalone:jewel-intUi-standalone",
28-
]
45+
],
46+
runtime_deps = [":jewel-intUi-standalone-tests"]
2947
)
3048
### auto-generated section `build intellij.platform.jewel.intUi.standalone.tests` end
3149

platform/jewel/int-ui/int-ui-standalone-tests/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins { jewel }
22

33
dependencies {
4+
api(projects.intUi.intUiStandalone)
45
testImplementation(kotlin("test"))
56
testImplementation(libs.junit.jupiter)
67
testRuntimeOnly(libs.junit.platform.engine)

platform/jewel/int-ui/int-ui-standalone-tests/intellij.platform.jewel.intUi.standalone.tests.iml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<exclude-output />
2121
<content url="file://$MODULE_DIR$">
2222
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
23+
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-resource" />
2324
</content>
2425
<orderEntry type="inheritedJdk" />
2526
<orderEntry type="sourceFolder" forTests="false" />
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
package org.jetbrains.jewel.intui.core.theme
3+
4+
import java.util.concurrent.Executors
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertTrue
7+
import org.jetbrains.jewel.intui.standalone.bundle.DynamicBundle
8+
import org.junit.jupiter.api.Test
9+
10+
private val MNEMONIC = 0x1B.toChar().toString()
11+
12+
internal class DynamicBundleTest {
13+
private val testBundle = DynamicBundle(DynamicBundle::class.java, "messages.TestBundle")
14+
15+
@Test
16+
fun loads_plain_message() {
17+
val msg = testBundle.getMessage("plain.hello")
18+
assertEquals("Hello", msg)
19+
}
20+
21+
@Test
22+
fun formats_parameterized_message() {
23+
val msg = testBundle.getMessage("hello.param", "World")
24+
assertEquals("Hello World", msg)
25+
}
26+
27+
@Test
28+
fun mnemonic_single_ampersand() {
29+
val msg = testBundle.getMessage("menu.open")
30+
// Current implementation inserts the mnemonic marker for single '&' on all OSes
31+
assertEquals(MNEMONIC + "Open", msg)
32+
}
33+
34+
@Test
35+
fun mnemonic_double_ampersand_differs_by_os() {
36+
val msg = testBundle.getMessage("menu.copy")
37+
// Accept both outcomes depending on the underlying OS
38+
val macExpectation = "Copy $MNEMONIC Paste"
39+
val nonMacExpectation = "Copy Paste"
40+
assertTrue(msg == macExpectation || msg == nonMacExpectation, "Unexpected processed value: '$msg'")
41+
}
42+
43+
@Test
44+
fun escapedAmpersand_doesNotAddMnemonic() {
45+
val msg = testBundle.getMessage("menu.copy.paste")
46+
assertEquals("Copy & Paste", msg)
47+
}
48+
49+
@Test
50+
fun concurrent_message_access_does_not_fail() {
51+
val pool = Executors.newFixedThreadPool(4)
52+
try {
53+
val tasks = (1..12).map { pool.submit<String> { testBundle.getMessage("plain.hello") } }
54+
tasks.forEach { assertEquals("Hello", it.get()) }
55+
} finally {
56+
pool.shutdownNow()
57+
}
58+
}
59+
60+
@Test
61+
fun english_ordinal_formatting_via_reflection() {
62+
assertEquals("Value: 1st", testBundle.getMessage("number.ordinal", 1))
63+
assertEquals("Value: 2nd", testBundle.getMessage("number.ordinal", 2))
64+
assertEquals("Value: 3rd", testBundle.getMessage("number.ordinal", 3))
65+
assertEquals("Value: 4th", testBundle.getMessage("number.ordinal", 4))
66+
assertEquals("Value: 11th", testBundle.getMessage("number.ordinal", 11))
67+
assertEquals("Value: 12th", testBundle.getMessage("number.ordinal", 12))
68+
assertEquals("Value: 13th", testBundle.getMessage("number.ordinal", 13))
69+
assertEquals("Value: 14th", testBundle.getMessage("number.ordinal", 14))
70+
assertEquals("Value: 21st", testBundle.getMessage("number.ordinal", 21))
71+
assertEquals("Value: 22nd", testBundle.getMessage("number.ordinal", 22))
72+
assertEquals("Value: 23rd", testBundle.getMessage("number.ordinal", 23))
73+
assertEquals("Value: 24th", testBundle.getMessage("number.ordinal", 24))
74+
assertEquals("Value: 101st", testBundle.getMessage("number.ordinal", 101))
75+
assertEquals("Value: 404th", testBundle.getMessage("number.ordinal", 404))
76+
}
77+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
plain.hello=Hello
3+
hello.param=Hello {0}
4+
menu.open=&Open
5+
menu.copy=Copy && Paste
6+
menu.copy.paste=Copy \\& Paste
7+
number.ordinal=Value: {0,number,ordinal}

platform/jewel/int-ui/int-ui-standalone/api-dump-experimental.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
*f:org.jetbrains.jewel.intui.standalone.JewelIntUIBundle
2+
- org.jetbrains.jewel.intui.standalone.bundle.AbstractDynamicBundle
3+
- sf:$stable:I
4+
- sf:INSTANCE:org.jetbrains.jewel.intui.standalone.JewelIntUIBundle
5+
*a:org.jetbrains.jewel.intui.standalone.bundle.AbstractDynamicBundle
6+
- sf:$stable:I
7+
- <init>(java.lang.Class,java.lang.String):V
8+
- f:getLazyMessage(java.lang.String,java.lang.Object[]):java.util.function.Supplier
9+
- f:getMessage(java.lang.String,java.lang.Object[]):java.lang.String
110
f:org.jetbrains.jewel.intui.standalone.styling.IntUiIconButtonStylingKt
211
- *sf:darkTransparentBackground(org.jetbrains.jewel.ui.component.styling.IconButtonStyle$Companion,org.jetbrains.jewel.ui.component.styling.IconButtonColors,org.jetbrains.jewel.ui.component.styling.IconButtonMetrics):org.jetbrains.jewel.ui.component.styling.IconButtonStyle
312
- *bs:darkTransparentBackground$default(org.jetbrains.jewel.ui.component.styling.IconButtonStyle$Companion,org.jetbrains.jewel.ui.component.styling.IconButtonColors,org.jetbrains.jewel.ui.component.styling.IconButtonMetrics,I,java.lang.Object):org.jetbrains.jewel.ui.component.styling.IconButtonStyle

platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/IntUiMessageResourceResolver.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.jetbrains.jewel.ui.util.MessageResourceResolver
1010
* associated with specific keys. It can be used to retrieve action text for UI elements, like buttons or menu items
1111
* based on the string keys from IntelliJ Platform resource bundle.
1212
*/
13-
internal class IntUiMessageResourceResolver : MessageResourceResolver {
13+
internal object IntUiMessageResourceResolver : MessageResourceResolver {
1414
/**
1515
* Fetches the string associated with a given key.
1616
*
@@ -21,11 +21,5 @@ internal class IntUiMessageResourceResolver : MessageResourceResolver {
2121
* message bundle (e.g, ""action.text.copy.link.address").
2222
* @return The string associated with the provided key. If the key is not found, an empty string is returned.
2323
*/
24-
override fun resolveIdeBundleMessage(key: String): String =
25-
when (key) {
26-
"action.text.open.link.in.browser" -> "Open Link in Browser"
27-
"action.text.copy.link.address" -> "Copy Link Address"
28-
"action.text.more" -> "More"
29-
else -> ""
30-
}
24+
override fun resolveIdeBundleMessage(key: String): String = JewelIntUIBundle.getMessage(key)
3125
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
package org.jetbrains.jewel.intui.standalone
3+
4+
import org.jetbrains.annotations.ApiStatus
5+
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
6+
import org.jetbrains.jewel.intui.standalone.bundle.DynamicBundle
7+
8+
/**
9+
* Loads the JewelBundle messages with required messages by Jewel.
10+
*
11+
* The counterpart of this in the LaF is the [com.intellij.ide.IdeBundle], which provides the necessary strings.
12+
*/
13+
@ApiStatus.Experimental
14+
@ExperimentalJewelApi
15+
public object JewelIntUIBundle : DynamicBundle(JewelIntUIBundle::class.java, "messages.JewelBundle")

0 commit comments

Comments
 (0)