Skip to content

Commit cff1fe4

Browse files
authored
feat: add documentation for Localization (#620)
1 parent 042d65b commit cff1fe4

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
sidebar_position: 11
3+
title: Localization
4+
sidebar_class_name: new-content
5+
---
6+
7+
# Localization <DocChip chip='since' label='25.10' />
8+
9+
Components implementing the `LocaleObserver` interface receive automatic notifications when the locale changes. This enables UI elements to update their text, formatting, and other locale-specific content without manual coordination.
10+
11+
## The `LocaleObserver` interface {#the-localeobserver-interface}
12+
13+
```java title="LocaleObserver.java"
14+
@FunctionalInterface
15+
public interface LocaleObserver extends Serializable {
16+
void onLocaleChange(LocaleEvent event);
17+
}
18+
```
19+
20+
When a component implements this interface, webforJ automatically:
21+
- Registers the component when created to receive locale change events
22+
- Unregisters the component when destroyed
23+
- Calls `onLocaleChange()` whenever the locale is changed
24+
25+
This registration happens through the component lifecycle.
26+
27+
## Handling translations {#handling-translations}
28+
29+
When `onLocaleChange()` is called, components receive the new locale. How they load and apply translations is up to the developer. Common approaches include:
30+
31+
- Java `ResourceBundle` with property files
32+
- Database queries for translations
33+
- Custom translation providers
34+
- Hard-coded maps for simple cases
35+
36+
This example uses `ResourceBundle`, which stores translations in property files:
37+
38+
```
39+
messages.properties # Fallback/default
40+
messages_en.properties # English
41+
messages_de.properties # German
42+
```
43+
44+
Property files contain key-value pairs:
45+
46+
```properties title="messages_en.properties"
47+
app.title=Mailbox
48+
menu.inbox=Inbox
49+
```
50+
51+
```properties title="messages_de.properties"
52+
app.title=Postfach
53+
menu.inbox=Posteingang
54+
```
55+
## Changing the locale {#changing-the-locale}
56+
57+
Use `App.setLocale()` to change the app locale. This triggers notifications to all registered observers:
58+
59+
```java
60+
App.setLocale(Locale.GERMAN);
61+
App.setLocale(Locale.forLanguageTag("fr"));
62+
```
63+
64+
A typical implementation could use a dropdown or choice component:
65+
66+
```java
67+
ChoiceBox languageSelector = new ChoiceBox();
68+
languageSelector.add("en", "English");
69+
languageSelector.add("de", "Deutsch");
70+
languageSelector.add("fr", "Français");
71+
72+
languageSelector.onSelect(e -> {
73+
String lang = (String) e.getSelectedItem().getKey();
74+
Locale newLocale = Locale.forLanguageTag(lang);
75+
76+
App.setLocale(newLocale);
77+
});
78+
```
79+
80+
When the user selects a language, `App.setLocale()` fires, and all components implementing `LocaleObserver` receive the update.
81+
82+
## Implementing observers {#implementing-observers}
83+
84+
When a component implements `LocaleObserver`, it needs to handle two scenarios: initial rendering with the current locale, and updating when the locale changes. The following example demonstrates this pattern with a component that displays localized text and links.
85+
86+
The component stores references to elements that need translation updates. When constructed, it loads the current locale's translations. When the locale changes, `onLocaleChange()` fires, allowing the component to reload translations and update its displayed text.
87+
88+
```java title="TranslationService.java"
89+
import com.webforj.App;
90+
import org.springframework.context.MessageSource;
91+
import org.springframework.stereotype.Service;
92+
93+
@Service
94+
public class TranslationService {
95+
private final MessageSource messageSource;
96+
97+
public TranslationService(MessageSource messageSource) {
98+
this.messageSource = messageSource;
99+
}
100+
101+
public String get(String key) {
102+
return messageSource.getMessage(key, null, App.getLocale());
103+
}
104+
}
105+
```
106+
107+
```java title="Explore.java"
108+
public class Explore extends Composite<FlexLayout> implements LocaleObserver {
109+
private final TranslationService i18n;
110+
private FlexLayout self = getBoundComponent();
111+
private H3 titleElement;
112+
private Anchor anchor;
113+
private String titleKey;
114+
115+
public Explore(TranslationService i18n, String titleKey) {
116+
this.i18n = i18n;
117+
this.titleKey = titleKey;
118+
119+
self.addClassName("explore-component");
120+
self.setStyle("margin", "1em auto");
121+
self.setDirection(FlexDirection.COLUMN);
122+
self.setAlignment(FlexAlignment.CENTER);
123+
self.setMaxWidth(300);
124+
self.setSpacing(".3em");
125+
126+
Img img = new Img(String.format("ws://explore/%s.svg", titleKey), "mailbox");
127+
img.setMaxWidth(250);
128+
129+
String translatedTitle = i18n.get("menu." + titleKey.toLowerCase());
130+
titleElement = new H3(translatedTitle);
131+
132+
anchor = new Anchor("https://docs.webforj.com/docs/components/overview", i18n.get("explore.link"));
133+
anchor.setTarget("_blank");
134+
135+
self.add(img, titleElement, anchor);
136+
}
137+
138+
@Override
139+
public void onLocaleChange(LocaleEvent event) {
140+
titleElement.setText(i18n.get("menu." + titleKey.toLowerCase()));
141+
anchor.setText(i18n.get("explore.link"));
142+
}
143+
}
144+
```
145+
146+
The component stores references to elements that display translated content (`titleElement` and `anchor`). Translations are loaded in the constructor using the current locale. When the locale changes, `onLocaleChange()` updates only the text that needs translation.
147+
148+
## Lifecycle management {#lifecycle-management}
149+
150+
The framework handles observer registration automatically through component lifecycle hooks:
151+
152+
- **On create**: Components implementing `LocaleObserver` are registered in `LocaleObserverRegistry`
153+
- **On destroy**: Components are unregistered to prevent memory leaks
154+
155+
Each app instance maintains its own observer registry. This automatic management means:
156+
157+
- No manual register/unregister calls
158+
- No memory leaks from destroyed components
159+
- Thread-safe concurrent notifications
160+
161+
:::info Per-app registry
162+
Each app instance maintains its own observer registry. Observers registered in one app don't receive notifications from other apps running in the same JVM.
163+
:::
164+
165+
## `LocaleEvent` {#localeevent}
166+
167+
The `LocaleEvent` passed to `onLocaleChange()` provides:
168+
169+
| Method | Returns | Description |
170+
|--------|---------|-------------|
171+
| `getLocale()` | `Locale` | The new locale that was set |
172+
| `getSource()` | `Object` | The component that received the event |
173+
174+
```java
175+
@Override
176+
public void onLocaleChange(LocaleEvent event) {
177+
Locale newLocale = event.getLocale();
178+
Object source = event.getSource();
179+
180+
// Update component using new locale
181+
ResourceBundle bundle = ResourceBundle.getBundle("messages", newLocale);
182+
updateUI(bundle);
183+
}
184+
```

0 commit comments

Comments
 (0)