[iDempiere] 多語系全域搜尋外掛:跨語言找選單,用英文找到中文功能

本篇文章更新時間:2026/06/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持


問題:多語系環境下,全域搜尋只認當前語言

iDempiere 支援多語系,你可以用繁體中文登入、用英文登入、用日文登入。但有一個體驗問題從來沒被解決:

全域搜尋(Alt+G)只能用當前登入語系搜尋選單項目。

具體來說:

  • 登入語系是 zh_TW,你輸入「Purchase Order」→ 找不到。你必須輸入「採購單」。
  • 登入語系是 en_US,你輸入「會計」→ 找不到。你必須輸入「Accounting Rules」。

這在跨國團隊或雙語環境中非常惱人。一個台灣工程師可能記得功能的英文名稱(因為文件是英文的),但系統設成中文;一個外國顧問可能知道中文名稱(因為客戶這樣叫),但系統設成英文。

更實際的場景:你在看英文教學文件,文件裡寫「open the Purchase Order window」,你得先在腦中翻譯成中文,再去搜尋「採購單」。每天不知道做幾次這種無意義的翻譯。

解決方案:跨語系搜尋,一行設定都不用

idempiere-multilang-search 是一個 iDempiere 外掛,安裝後全域搜尋自動支援所有已翻譯語系的比對。

Before:登入 zh_TW,只能用中文搜尋。

After:登入 zh_TW,輸入「Purchase Order」→ 找到「採購單」。輸入「採購」也能找到。兩種語言同時比對。

反過來也一樣:登入 en_US,輸入「會計」→ 找到「Accounting Rules」。

不需要任何設定。安裝、重啟、完成。


系統需求

  • iDempiere 12 或更新版本
  • 至少 2 個啟用的語系,且選單項目已翻譯(System Admin → Language → Verify Language)

安裝方式

方式一:update-prd.sh(建議)

cd /opt/idempiere
./update-prd.sh file:///path/to/extracted/repository tw.idempiere.multilang.search
systemctl restart idempiere

方式二:Felix Web Console

  1. Releases 下載 plugin JAR
  2. 開啟 http://<server>:8080/osgi/system/console/bundles(帳號:SuperUser / System)
  3. 點 Install/Update,選擇 JAR,點 Install or Update
  4. 重啟 iDempiere

方式三:Docker

services:
  idempiere:
    image: idempiereofficial/idempiere:12-release
    entrypoint:
      - bash
      - -c
      - |
        cp /custom-plugins/*.jar /opt/idempiere/plugins/
        grep -q multilang /opt/idempiere/configuration/org.eclipse.equinox.simpleconfigurator/bundles.info 2>/dev/null || \
          echo 'tw.idempiere.multilang.search,1.0.0,plugins/tw.idempiere.multilang.search_1.0.0.jar,4,false' >> /opt/idempiere/configuration/org.eclipse.equinox.simpleconfigurator/bundles.info
        exec ./docker-entrypoint.sh idempiere
    volumes:
      - ./plugins:/custom-plugins:ro

驗證安裝成功

重啟後檢查 server log,應出現:

Multi-Language Global Search plugin initialized
Multi-Language Search: loaded N alternative labels for M menu items
Multi-Language Global Search patched successfully

解除安裝

移除外掛後重啟即可。沒有資料庫變更、沒有殘留設定,系統恢復原始行為。


技術深入:為什麼是 OSGi Fragment

這個外掛和我之前介紹的 看板外掛預約管理外掛 在架構上有根本差異。前兩個是 WAB(Web Application Bundle)— 獨立的 web 應用程式,有自己的 servlet 和靜態資源。這個則是 OSGi Fragment

Fragment vs Bundle

特性 Bundle(WAB) Fragment
獨立性 有自己的 classloader、lifecycle 附加到 host bundle,共享 classloader
啟動狀態 Active(state=32) Resolved/Fragment(state=4),不會是 Active
適用場景 新增獨立功能(新頁面、新 API) 擴充/修補 host bundle 的既有功能
存取範圍 只能存取 host 的 exported packages 可存取 host 的所有 packages(含 private)

為什麼這裡必須用 Fragment?因為我們要修改的目標 — GlobalSearchMenuSearchController — 是 iDempiere ZK Web UI 的內部類別。它們的關鍵 field 是 private 的,正常 bundle 無法存取。Fragment 附加到 host bundle(org.adempiere.ui.zk)後,共享同一個 classloader,可以存取所有內部類別。

為什麼不直接改原始碼

這是 iDempiere 外掛開發的鐵律:不改核心。改了核心,每次升版都是噩夢。OSGi Fragment 讓你在不動核心一行程式碼的前提下,「插入」到核心的運行過程中。


技術深入:Runtime Patching 機制

全域搜尋元件(GlobalSearch)是在 ZK Desktop 建立時由 HeaderPanel 初始化的。我們不能覆蓋它的 ZUL 檔案(多 fragment 的資源載入順序不可控),所以採用 Runtime Patching — 在元件建立之後、使用者操作之前,把舊元件替換成增強版。

流程

  1. 註冊 UiLifeCycle listener:透過 ZK 的 metainfo/zk/config.xml 機制,fragment 在 classpath 中放置設定檔,ZK 啟動時自動掃描並註冊 listener。
  2. 攔截 Desktop 建立:listener 的 afterComponentAttached 偵測到 HeaderPanel 被掛載時觸發。
  3. 延遲到正確時機:使用 Events.echoEvent 延遲執行 patch,確保所有元件已完成初始化。echoEvent 的機制是 server → client → server 一次往返,保證在 UI 完整渲染後才執行。
  4. 替換元件:用 Reflection 取得 HeaderPanel.globalSearch(private field),取出舊的 MenuSearchController 中的 tree,建立增強版的 MultiLangMenuSearchController,再建立新的 GlobalSearch 替換舊的。

Graceful Degradation

整個 patching 過程包在 try-catch 中。如果任何步驟失敗(例如 iDempiere 升版改了 field 名稱),系統 fallback 到原始行為 — 全域搜尋仍然可用,只是回到單語系比對。不會壞掉,只是失去多語系功能。


技術深入:跨語系比對邏輯

核心修改在 MultiLangMenuSearchController 的比對器中。原始的 MenuListComparator 只比對 MenuItem.getLabel()(當前語系的顯示文字)。增強版額外比對一張 altLabelsMap

altLabelsMap 的建立

使用者登入後,外掛執行一次 SQL 查詢:

  1. AD_Menu_Trl:取得所有「非當前語系」的已翻譯選單名稱。
  2. AD_Menu 基礎表:如果使用者不是基礎語系(en_US),也把英文名稱加入。

結果是一個 Map<Integer, List<String>>:每個 AD_Menu_ID 對應一組「其他語系的名稱」。

比對流程

使用者輸入 "Purchase Order"
  → 先比對當前語系 label(zh_TW: "採購單")→ 不符合
  → 再比對 altLabelsMap 中該 menu 的其他語系名稱
    → 找到 "Purchase Order"(en_US)→ 符合!
  → 結果顯示「採購單」

效能影響極小:altLabelsMap 在登入時載入一次(通常幾百筆),搜尋時只是 in-memory 的字串比對。


設計原則

這個外掛體現了我認為 iDempiere plugin 開發應該追求的幾個原則:

  • 零設定:安裝即生效。不需要使用者去後台開任何開關。
  • 零殘留:移除後系統完全恢復原狀。沒有資料庫變更、沒有 AD 記錄、沒有設定檔。
  • Graceful degradation:失敗時 fallback 到原始行為,不會破壞系統。
  • 不改核心:所有擴充透過 OSGi 機制完成,核心升級不受影響。

授權

GPL v2 — 和 iDempiere 相同。


延伸閱讀

本站 iDempiere 系列:

如果你的 iDempiere 環境有多語系需求,或需要客製 plugin 開發協助,歡迎透過聯絡我們洽詢。


Share:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon