硬編碼字串散落代碼各處,維護困難且無法多語言。J2ME 無標準 ResourceBundle,需自行實現輕量級字串資源管理器。本文設計支援 **靜態存取、資源檔外部化、多語言熱切換、命名空間隔離** 的 StringManager。
StringManager.get("MainMenu.Exit")setLang("en") 即時生效類名.功能鍵 (如 GameCanvas.Score) 避免衝突key=value 或 key\tvalue,換行分隔/res/strings/ ├── game.zh_TW # 繁體中文 ├── game.en # 英文 └── game.ja # 日文
檔案內容範例 (game.zh_TW):
MainMenu.Start 開始遊戲
MainMenu.Option 設定
MainMenu.Exit 離開
GameCanvas.Score 分數:
GameCanvas.Life 生命:
Alert.GameOver 遊戲結束
Alert.NewRecord 新紀錄!
分隔符支援 空格、\t、=,解析器自適應。
public class StringManager {
private static StringManager instance; // 單例
private String currentLang = "zh_TW";
private String currentRes = "game";
private Hashtable table = new Hashtable();
private boolean loaded = false;
private StringManager(String lang) { this.currentLang = lang; }
// 靜態工廠:取得單例並可切換語言
public static StringManager getInstance(String lang) {
if (instance == null) instance = new StringManager(lang);
else if (!instance.currentLang.equals(lang)) instance.setLang(lang);
return instance;
}
// 靜態快捷存取
public static String get(String key) {
return (instance != null) ? instance.getString(key) : key;
}
// 切換語言 (重置載入狀態)
public void setLang(String lang) {
if (!currentLang.equals(lang)) {
currentLang = lang;
loaded = false;
}
}
// 載入指定資源檔
public void load(String resName) {
if (loaded && currentRes.equals(resName)) return;
currentRes = resName;
table.clear();
InputStream is = getClass().getResourceAsStream("/res/strings/" + resName + "." + currentLang);
if (is == null) { loaded = true; return; } // 找不到檔案靜默失敗
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) bos.write(buf, 0, len);
String content = new String(bos.toByteArray()); // 資源檔存 UTF-16BE 可直接 new String(bytes)
parseContent(content);
loaded = true;
} catch (IOException e) {
System.err.println("Load strings failed: " + e);
} finally {
try { is.close(); } catch (IOException ignored) {}
}
}
// 解析 key=value 格式
private void parseContent(String content) {
int i = 0, n = content.length();
StringBuilder key = new StringBuilder();
StringBuilder val = new StringBuilder();
boolean readingKey = true;
while (i < n) {
char c = content.charAt(i++);
if (readingKey) {
if (c == ' ' || c == '\t' || c == '=') {
readingKey = false;
} else {
key.append(c);
}
} else {
if (c == '\n' || c == '\r') {
String k = key.toString().trim();
String v = val.toString().trim();
if (k.length() > 0) table.put(k, v);
key.setLength(0); val.setLength(0);
readingKey = true;
} else {
val.append(c);
}
}
}
// 最後一行
String k = key.toString().trim();
String v = val.toString().trim();
if (k.length() > 0) table.put(k, v);
}
public String getString(String key) {
Object v = table.get(key);
return (v != null) ? (String)v : key; // 找不到回傳鍵名,便於除錯
}
}
// MIDlet 啟動時初始化
StringManager.getInstance("zh_TW").load("game");
// 代碼中取用
String exitLabel = StringManager.get("MainMenu.Exit"); // → "離開"
String scoreFmt = StringManager.get("GameCanvas.Score"); // → "分數:"
// 語言切換 (如選單選項)
public void switchLanguage(String lang) {
StringManager.getInstance(lang).load("game");
// 刷新當前顯示畫面
currentDisplayable.repaint();
}
| 需求 | 擴展方向 |
|---|---|
| 多資源檔同時載入 | Hashtable 單例管理多個 resource name |
| 參數化字串 (printf) | 值存 "分數:%d",get(key, args...) 內部 String.format (MIDP 2.0) 或手工替換 |
| 複數形式支援 | 引入 key[0] key[1] 區分單複數,或整合 ICU4J 輕量子集 |
| 資源檔加密/壓縮 | 建構期預處理加密,載入時解密解壓 |
| IDE 整合 | 編寫 Eclipse 外掛/腳本:掃描代碼 StringManager.get() 自動同步資源檔鍵值 |
StringManager 以 單例 + 靜態門面 + 純文本資源 + 簡易解析器 實現 J2ME 字串資源化與多語言。核心優勢:零依賴、記憶體佔用極低 (單語言約 2~5KB)、熱切換無需重啟。配合建構腳本自動提取鍵值,可構建專業級本地化流程。