Skip to content

Commit 7e4d75d

Browse files
committed
feat(i18n): Enhance i18n support with configurable resource bundle base name
1 parent 2cf5b37 commit 7e4d75d

File tree

4 files changed

+54
-46
lines changed

4 files changed

+54
-46
lines changed

src/main/java/com/github/codeboyzhou/mcp/declarative/annotation/McpI18nEnabled.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* <p>Example usage:
1919
*
2020
* <pre>{@code
21-
* @McpI18nEnabled
21+
* @McpI18nEnabled(resourceBundleBaseName = "mcp_server_component_i18n")
2222
* public class MyMcpServerApplication {
2323
* // Application logic...
2424
* }
@@ -28,4 +28,16 @@
2828
*/
2929
@Target(ElementType.TYPE)
3030
@Retention(RetentionPolicy.RUNTIME)
31-
public @interface McpI18nEnabled {}
31+
public @interface McpI18nEnabled {
32+
/**
33+
* Specifies the base name of the resource bundle to be used for i18n.
34+
*
35+
* <p>The resource bundle base name should follow the standard Java resource bundle naming
36+
* convention. For example, if the resource bundle base name is "messages", the corresponding
37+
* resource bundle files should be named "messages.properties", "messages_en_US.properties",
38+
* "messages_zh_CN.properties", etc.
39+
*
40+
* @return the base name of the resource bundle
41+
*/
42+
String resourceBundleBaseName();
43+
}

src/main/java/com/github/codeboyzhou/mcp/declarative/di/GuiceInjectorModule.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818
import com.github.codeboyzhou.mcp.declarative.server.component.McpServerTool;
1919
import com.github.codeboyzhou.mcp.declarative.server.converter.McpPromptParameterConverter;
2020
import com.github.codeboyzhou.mcp.declarative.server.converter.McpToolParameterConverter;
21+
import com.github.codeboyzhou.mcp.declarative.util.StringHelper;
2122
import com.google.inject.AbstractModule;
2223
import com.google.inject.Provides;
2324
import com.google.inject.Singleton;
24-
import com.google.inject.name.Names;
2525
import java.lang.annotation.Annotation;
2626
import java.lang.reflect.Method;
27+
import java.util.Locale;
28+
import java.util.ResourceBundle;
2729
import java.util.Set;
2830
import org.reflections.Reflections;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
2933

3034
/**
3135
* This class is a Guice module that configures bindings for classes annotated with {@link
@@ -35,8 +39,7 @@
3539
*/
3640
public final class GuiceInjectorModule extends AbstractModule {
3741

38-
/** The name of the injected variable for i18n enabled. */
39-
public static final String INJECTED_VARIABLE_NAME_I18N_ENABLED = "i18nEnabled";
42+
private static final Logger log = LoggerFactory.getLogger(GuiceInjectorModule.class);
4043

4144
/** The main class to use for configuration. */
4245
private final Class<?> mainClass;
@@ -70,12 +73,6 @@ protected void configure() {
7073
bind(McpStdioServer.class).in(SINGLETON);
7174
bind(McpSseServer.class).in(SINGLETON);
7275
bind(McpStreamableServer.class).in(SINGLETON);
73-
74-
// Bind for boolean variable: i18nEnabled
75-
final boolean i18nEnabled = mainClass.isAnnotationPresent(McpI18nEnabled.class);
76-
bind(Boolean.class)
77-
.annotatedWith(Names.named(INJECTED_VARIABLE_NAME_I18N_ENABLED))
78-
.toInstance(i18nEnabled);
7976
}
8077

8178
/**
@@ -91,6 +88,28 @@ public Reflections provideReflections() {
9188
return new Reflections(basePackage, MethodsAnnotated, FieldsAnnotated);
9289
}
9390

91+
/**
92+
* Provides a {@link ResourceBundle} instance for the main class.
93+
*
94+
* @return a {@link ResourceBundle} instance for the main class
95+
*/
96+
@Provides
97+
@Singleton
98+
public ResourceBundle provideResourceBundle() {
99+
McpI18nEnabled mcpI18nEnabled = mainClass.getAnnotation(McpI18nEnabled.class);
100+
if (mcpI18nEnabled == null) {
101+
log.info("McpI18nEnabled annotation is not present on the main class, skip i18n support.");
102+
return null;
103+
}
104+
105+
final String baseName = mcpI18nEnabled.resourceBundleBaseName();
106+
if (StringHelper.isBlank(baseName)) {
107+
throw new IllegalArgumentException("resourceBundleBaseName must not be blank.");
108+
}
109+
110+
return ResourceBundle.getBundle(baseName, Locale.getDefault());
111+
}
112+
94113
/**
95114
* Determines the base package for the {@link Reflections} instance to scan.
96115
*
Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package com.github.codeboyzhou.mcp.declarative.server.component;
22

3-
import static com.github.codeboyzhou.mcp.declarative.di.GuiceInjectorModule.INJECTED_VARIABLE_NAME_I18N_ENABLED;
4-
53
import com.github.codeboyzhou.mcp.declarative.di.DependencyInjector;
64
import com.github.codeboyzhou.mcp.declarative.di.DependencyInjectorProvider;
75
import com.github.codeboyzhou.mcp.declarative.util.StringHelper;
8-
import java.util.Locale;
96
import java.util.ResourceBundle;
10-
import org.slf4j.Logger;
11-
import org.slf4j.LoggerFactory;
127

138
/**
149
* This abstract class represents an MCP server component (resource/prompt/tool) that is responsible
@@ -19,12 +14,6 @@
1914
* @author codeboyzhou
2015
*/
2116
public abstract class AbstractMcpServerComponent<T> implements McpServerComponent<T> {
22-
23-
private static final Logger log = LoggerFactory.getLogger(AbstractMcpServerComponent.class);
24-
25-
/** The base name of the resource bundle for MCP server component descriptions. */
26-
private static final String RESOURCE_BUNDLE_BASE_NAME = "i18n/mcp_server_component_descriptions";
27-
2817
/** The default value to use when a component attribute is not specified. */
2918
protected static final String NOT_SPECIFIED = "Not specified";
3019

@@ -34,14 +23,10 @@ public abstract class AbstractMcpServerComponent<T> implements McpServerComponen
3423
/** The resource bundle to use for localizing component descriptions. */
3524
private final ResourceBundle bundle;
3625

37-
/** Whether to enable i18n for component descriptions. */
38-
private final boolean i18nEnabled;
39-
4026
/** Creates a new instance of {@code AbstractMcpServerComponent}. */
4127
protected AbstractMcpServerComponent() {
42-
this.bundle = loadResourceBundle();
4328
this.injector = DependencyInjectorProvider.INSTANCE.getInjector();
44-
this.i18nEnabled = injector.getVariable(Boolean.class, INJECTED_VARIABLE_NAME_I18N_ENABLED);
29+
this.bundle = injector.getInstance(ResourceBundle.class);
4530
}
4631

4732
/**
@@ -52,27 +37,9 @@ protected AbstractMcpServerComponent() {
5237
* @return the resolved value of the component attribute
5338
*/
5439
protected String resolveComponentAttributeValue(String attributeLiteralValue) {
55-
if (i18nEnabled && bundle != null && bundle.containsKey(attributeLiteralValue)) {
40+
if (bundle != null && bundle.containsKey(attributeLiteralValue)) {
5641
return bundle.getString(attributeLiteralValue);
5742
}
5843
return StringHelper.defaultIfBlank(attributeLiteralValue, NOT_SPECIFIED);
5944
}
60-
61-
/**
62-
* Loads the resource bundle for MCP server component descriptions, using the default locale.
63-
*
64-
* @return the resource bundle for MCP server component descriptions
65-
*/
66-
private ResourceBundle loadResourceBundle() {
67-
Locale locale = Locale.getDefault();
68-
try {
69-
return ResourceBundle.getBundle(RESOURCE_BUNDLE_BASE_NAME, locale);
70-
} catch (Exception e) {
71-
log.warn(
72-
"Can't find resource bundle for base name: {}, locale {}, i18n will be unsupported",
73-
RESOURCE_BUNDLE_BASE_NAME,
74-
locale);
75-
return null;
76-
}
77-
}
7845
}

src/main/java/com/github/codeboyzhou/mcp/declarative/util/StringHelper.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ public final class StringHelper {
2828
throw new UnsupportedOperationException("Utility class should not be instantiated");
2929
}
3030

31+
/**
32+
* Checks if the given string is blank.
33+
*
34+
* @param str the string to check
35+
* @return {@code true} if the string is blank, {@code false} otherwise
36+
*/
37+
public static boolean isBlank(String str) {
38+
return str == null || str.isBlank();
39+
}
40+
3141
/**
3242
* Returns the default value if the given string is blank, otherwise returns the original string.
3343
*

0 commit comments

Comments
 (0)