0
# Translation System
1
2
Internationalization support with locale-based rendering, translation registries, and global translation management. Adventure's translation system enables multi-language support for text components.
3
4
## Capabilities
5
6
### Translatable Components
7
8
Components that display translated text based on the client's locale and registered translations.
9
10
```java { .api }
11
/**
12
* Component that displays translated text
13
*/
14
interface TranslatableComponent extends BuildableComponent<TranslatableComponent, TranslatableComponent.Builder> {
15
/**
16
* Gets the translation key
17
* @return the translation key
18
*/
19
String key();
20
21
/**
22
* Gets the translation arguments
23
* @return list of translation arguments
24
*/
25
List<TranslationArgument> arguments();
26
27
/**
28
* Gets the fallback string used when translation is not available
29
* @return the fallback string or null
30
*/
31
@Nullable String fallback();
32
33
/**
34
* Sets the translation key
35
* @param key the translation key
36
* @return component with new key
37
*/
38
TranslatableComponent key(String key);
39
40
/**
41
* Sets the translation arguments
42
* @param arguments the arguments
43
* @return component with new arguments
44
*/
45
TranslatableComponent arguments(List<? extends TranslationArgumentLike> arguments);
46
TranslatableComponent arguments(TranslationArgumentLike... arguments);
47
48
/**
49
* Sets the fallback string
50
* @param fallback the fallback string
51
* @return component with new fallback
52
*/
53
TranslatableComponent fallback(@Nullable String fallback);
54
}
55
```
56
57
### Translation Arguments
58
59
Arguments that can be passed to translatable components for parameter substitution.
60
61
```java { .api }
62
/**
63
* Argument for translatable components
64
*/
65
interface TranslationArgument {
66
/**
67
* Converts this argument to a component
68
* @return the component representation
69
*/
70
Component asComponent();
71
72
/**
73
* Converts this argument to a string
74
* @return the string representation
75
*/
76
String asString();
77
78
/**
79
* Creates a component argument
80
* @param component the component
81
* @return translation argument
82
*/
83
static TranslationArgument component(ComponentLike component);
84
85
/**
86
* Creates a string argument
87
* @param string the string
88
* @return translation argument
89
*/
90
static TranslationArgument string(String string);
91
92
/**
93
* Creates a numeric argument
94
* @param number the number
95
* @return translation argument
96
*/
97
static TranslationArgument numeric(Number number);
98
99
/**
100
* Creates a boolean argument
101
* @param bool the boolean value
102
* @return translation argument
103
*/
104
static TranslationArgument bool(boolean bool);
105
}
106
107
/**
108
* Objects that can be used as translation arguments
109
*/
110
interface TranslationArgumentLike {
111
/**
112
* Converts to a translation argument
113
* @return the translation argument
114
*/
115
TranslationArgument asTranslationArgument();
116
}
117
```
118
119
### Translator Interface
120
121
Core interface for translating keys to formatted messages based on locale.
122
123
```java { .api }
124
/**
125
* Interface for translating keys to messages
126
*/
127
interface Translator {
128
/**
129
* Translates a key to a message format
130
* @param key the translation key
131
* @param locale the target locale
132
* @return the message format or null if not found
133
*/
134
@Nullable MessageFormat translate(String key, Locale locale);
135
136
/**
137
* Creates an empty translator
138
* @return empty translator
139
*/
140
static Translator empty();
141
142
/**
143
* Creates a translator from a resource bundle
144
* @param bundle the resource bundle
145
* @return new translator
146
*/
147
static Translator translator(ResourceBundle bundle);
148
149
/**
150
* Creates a translator from a map of translations
151
* @param translations the translation map (key -> message)
152
* @return new translator
153
*/
154
static Translator translator(Map<String, String> translations);
155
}
156
```
157
158
### Global Translator
159
160
Global translation service that manages multiple translation sources.
161
162
```java { .api }
163
/**
164
* Global translation service for the entire application
165
*/
166
interface GlobalTranslator extends Translator {
167
/**
168
* Adds a translation source
169
* @param translator the translator to add
170
*/
171
void addSource(Translator translator);
172
173
/**
174
* Removes a translation source
175
* @param translator the translator to remove
176
*/
177
void removeSource(Translator translator);
178
179
/**
180
* Gets the default global translator instance
181
* @return the global translator
182
*/
183
static GlobalTranslator translator();
184
}
185
```
186
187
### Translation Registry
188
189
Registry interface for managing translations with locale support.
190
191
```java { .api }
192
/**
193
* Registry for translation keys and their translations
194
*/
195
interface TranslationRegistry extends Translator {
196
/**
197
* Registers a translation
198
* @param key the translation key
199
* @param locale the locale
200
* @param format the message format
201
*/
202
void register(String key, Locale locale, MessageFormat format);
203
204
/**
205
* Registers a simple string translation
206
* @param key the translation key
207
* @param locale the locale
208
* @param message the message string
209
*/
210
void register(String key, Locale locale, String message);
211
212
/**
213
* Unregisters a translation
214
* @param key the translation key
215
*/
216
void unregister(String key);
217
218
/**
219
* Gets all registered keys
220
* @return set of translation keys
221
*/
222
Set<String> keys();
223
224
/**
225
* Creates a new translation registry
226
* @return new registry
227
*/
228
static TranslationRegistry create();
229
}
230
```
231
232
### Translation Store
233
234
Storage system for translations with hierarchy support.
235
236
```java { .api }
237
/**
238
* Storage system for translations
239
*/
240
interface TranslationStore extends Translator {
241
/**
242
* Gets all available locales
243
* @return set of locales
244
*/
245
Set<Locale> locales();
246
247
/**
248
* Gets all translation keys
249
* @return set of keys
250
*/
251
Set<String> keys();
252
253
/**
254
* Checks if a translation exists
255
* @param key the translation key
256
* @param locale the locale
257
* @return true if translation exists
258
*/
259
boolean contains(String key, Locale locale);
260
}
261
262
/**
263
* Abstract base for translation storage implementations
264
*/
265
abstract class AbstractTranslationStore implements TranslationStore {
266
/**
267
* Template method for loading translations
268
* @param key the translation key
269
* @param locale the locale
270
* @return the message format or null
271
*/
272
protected abstract @Nullable MessageFormat loadTranslation(String key, Locale locale);
273
}
274
```
275
276
**Usage Examples:**
277
278
```java
279
import net.kyori.adventure.text.Component;
280
import net.kyori.adventure.translation.*;
281
import java.util.Locale;
282
import java.text.MessageFormat;
283
284
// Basic translatable component
285
Component welcome = Component.translatable("welcome.message");
286
287
// Translatable with arguments
288
Component playerJoined = Component.translatable("player.joined",
289
Component.text("PlayerName").color(NamedTextColor.YELLOW));
290
291
// With fallback for missing translations
292
Component customMessage = Component.translatable()
293
.key("custom.greeting")
294
.fallback("Hello, {0}!")
295
.arguments(TranslationArgument.string("World"))
296
.build();
297
298
// Register translations globally
299
GlobalTranslator global = GlobalTranslator.translator();
300
TranslationRegistry registry = TranslationRegistry.create();
301
302
registry.register("welcome.message", Locale.ENGLISH, "Welcome to the server!");
303
registry.register("welcome.message", Locale.FRENCH, "Bienvenue sur le serveur!");
304
registry.register("player.joined", Locale.ENGLISH, "{0} joined the game");
305
registry.register("player.joined", Locale.FRENCH, "{0} a rejoint la partie");
306
307
global.addSource(registry);
308
```
309
310
## Translation Patterns
311
312
### Resource Bundle Integration
313
314
```java
315
public class ResourceBundleTranslations {
316
public static void loadTranslations(String baseName) {
317
GlobalTranslator global = GlobalTranslator.translator();
318
319
// Load translations for different locales
320
for (Locale locale : getSupportedLocales()) {
321
try {
322
ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale);
323
Translator translator = Translator.translator(bundle);
324
global.addSource(translator);
325
} catch (MissingResourceException e) {
326
// Handle missing resource bundle
327
logger.warn("Missing resource bundle for locale: " + locale);
328
}
329
}
330
}
331
332
private static Set<Locale> getSupportedLocales() {
333
return Set.of(
334
Locale.ENGLISH,
335
Locale.FRENCH,
336
Locale.GERMAN,
337
Locale.SPANISH,
338
new Locale("es", "MX") // Mexican Spanish
339
);
340
}
341
}
342
```
343
344
### Dynamic Translation Management
345
346
```java
347
public class DynamicTranslations {
348
private final TranslationRegistry registry = TranslationRegistry.create();
349
350
public void addTranslation(String key, Locale locale, String message) {
351
registry.register(key, locale, message);
352
}
353
354
public void addFormattedTranslation(String key, Locale locale, String pattern) {
355
MessageFormat format = new MessageFormat(pattern, locale);
356
registry.register(key, locale, format);
357
}
358
359
public void loadFromProperties(String propertyFile, Locale locale) {
360
Properties props = loadProperties(propertyFile);
361
for (String key : props.stringPropertyNames()) {
362
registry.register(key, locale, props.getProperty(key));
363
}
364
}
365
366
public Component createTranslatable(String key, Object... args) {
367
TranslationArgument[] arguments = Arrays.stream(args)
368
.map(this::toTranslationArgument)
369
.toArray(TranslationArgument[]::new);
370
371
return Component.translatable(key, arguments);
372
}
373
374
private TranslationArgument toTranslationArgument(Object obj) {
375
if (obj instanceof Component) return TranslationArgument.component((Component) obj);
376
if (obj instanceof Number) return TranslationArgument.numeric((Number) obj);
377
if (obj instanceof Boolean) return TranslationArgument.bool((Boolean) obj);
378
return TranslationArgument.string(String.valueOf(obj));
379
}
380
}
381
```
382
383
### Locale-Aware Messaging
384
385
```java
386
public class LocalizedMessaging {
387
private final GlobalTranslator translator = GlobalTranslator.translator();
388
389
public void sendLocalizedMessage(Audience audience, String key, Object... args) {
390
// Get player's locale (implementation-specific)
391
Locale playerLocale = getPlayerLocale(audience);
392
393
// Create translatable component
394
Component message = Component.translatable(key,
395
Arrays.stream(args)
396
.map(TranslationArgument::string)
397
.toArray(TranslationArgument[]::new));
398
399
// Send with locale context
400
audience.sendMessage(message);
401
}
402
403
public void broadcastLocalized(Collection<? extends Audience> audiences, String key, Object... args) {
404
Component message = Component.translatable(key,
405
Arrays.stream(args)
406
.map(TranslationArgument::string)
407
.toArray(TranslationArgument[]::new));
408
409
for (Audience audience : audiences) {
410
audience.sendMessage(message);
411
}
412
}
413
414
public Component formatNumber(Number number, Locale locale) {
415
NumberFormat formatter = NumberFormat.getInstance(locale);
416
return Component.text(formatter.format(number));
417
}
418
419
public Component formatTime(long timestamp, Locale locale) {
420
DateFormat formatter = DateFormat.getDateTimeInstance(
421
DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
422
return Component.text(formatter.format(new Date(timestamp)));
423
}
424
}
425
```
426
427
### Translation Validation
428
429
```java
430
public class TranslationValidator {
431
public void validateTranslations(TranslationStore store) {
432
Set<String> keys = store.keys();
433
Set<Locale> locales = store.locales();
434
435
for (String key : keys) {
436
for (Locale locale : locales) {
437
if (!store.contains(key, locale)) {
438
logger.warn("Missing translation: {} for locale: {}", key, locale);
439
}
440
441
// Validate message format
442
MessageFormat format = store.translate(key, locale);
443
if (format != null) {
444
validateMessageFormat(key, locale, format);
445
}
446
}
447
}
448
}
449
450
private void validateMessageFormat(String key, Locale locale, MessageFormat format) {
451
try {
452
// Test with sample arguments
453
format.format(new Object[]{"test", 123, true});
454
} catch (IllegalArgumentException e) {
455
logger.error("Invalid message format for {}: {} ({})", key, locale, e.getMessage());
456
}
457
}
458
459
public Set<String> findMissingTranslations(Set<String> requiredKeys, TranslationStore store) {
460
return requiredKeys.stream()
461
.filter(key -> store.locales().stream()
462
.noneMatch(locale -> store.contains(key, locale)))
463
.collect(Collectors.toSet());
464
}
465
}
466
```
467
468
## Best Practices
469
470
### Translation Key Conventions
471
- Use hierarchical keys: `menu.main.title`, `error.permission.denied`
472
- Keep keys descriptive and consistent
473
- Use namespaces for different modules: `plugin.feature.message`
474
475
### Argument Handling
476
- Use typed arguments (numbers, booleans) when possible
477
- Provide meaningful argument names in translation files
478
- Consider argument order differences between languages
479
480
### Fallback Strategies
481
- Always provide fallback text for custom keys
482
- Use English as the base language for fallbacks
483
- Handle missing translations gracefully
484
485
### Performance Considerations
486
- Cache frequently used translations
487
- Load translations at startup, not runtime
488
- Use global translator efficiently to avoid duplicate lookups