撰寫應用程式(外掛程式)
應用程式從內部為 Antimatter 新增功能:一個以 Go 撰寫、掛載到伺服器的伺服器端,以及一個以 React 撰寫、新增選單、面板、訊息類型與頁面的 Web 應用程式端。本頁以隨附的投票(Polls)應用程式為例,展示應用程式的結構,以及 Antimatter 的 Fusion 介面有何不同。
應用程式的運作方式
- 伺服器端是一個 Go 程式。伺服器會將其啟動為獨立程序,並透過 RPC 與其通訊:伺服器呼叫應用程式的 hooks(例如訊息即將發布、指令已執行……),應用程式則呼叫伺服器的 plugin API(建立貼文、讀取使用者、儲存資料……)。它也可以在
/plugins/<app id>/下提供 HTTP 路由。 - Web 應用程式端是一個 JavaScript bundle,會與 Web 應用程式一起載入瀏覽器。它透過 註冊表 註冊 React 元件與回呼:選單中的按鈕、面板、繪製訊息類型的方式。
- 兩端皆為選填。應用程式以
.tar.gzbundle 形式安裝,由系統管理員啟用、停用並設定。
Antimatter 可執行為 Mattermost 撰寫的應用程式,反之亦然,只要應用程式僅使用兩者都有的 hooks。請參閱 Antimatter 獨有的 Hooks。
Bundle 結構
沒有獨立的起始範本:請從小型的隨附應用程式開始,例如 antimatter-plugin-polls 或 antimatter-plugin-todo,並修改其 ID 與程式碼。Polls 的結構如下:
antimatter-plugin-polls/
├── plugin.json the manifest
├── Makefile build, test, bundle, deploy
├── build/ the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg files shipped with the bundle
├── go.mod
├── server/
│ ├── main.go plugin.ClientMain(&Plugin{})
│ ├── plugin.go the Plugin type, OnActivate, ServeHTTP
│ ├── manifest.go generated from plugin.json
│ ├── api.go, command.go, hooks.go, store.go …
│ └── dist/ compiled binaries, one per platform
└── webapp/
├── package.json, webpack.config.js, tsconfig.json
├── i18n/ en.json and one file per language
├── src/index.tsx registers the app with the web app
├── src/manifest.ts generated from plugin.json
├── src/translations.ts
└── dist/main.js the bundle
選填:public/ 資料夾,會原樣提供於 /plugins/<app id>/public/。
plugin.json
{
"id": "com.example.weather",
"name": "Weather",
"description": "Forecasts in the chat.",
"homepage_url": "https://github.com/example/weather",
"support_url": "https://github.com/example/weather/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "12.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"linux-arm64": "server/dist/plugin-linux-arm64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"darwin-arm64": "server/dist/plugin-darwin-arm64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {"bundle_path": "webapp/dist/main.js"},
"settings_schema": {"settings": []}
}
id:唯一且永久;通常使用反向域名。它用於命名路由、儲存的設定與 WebSocket 事件。version:語意化版本。隨附的應用程式會將其從plugin.json中省略;其建置工具會從最新的v*git tag 取得。min_server_version:Antimatter 報告12.0.0。settings_schema:系統控制台中應用程式的設定頁面(請參閱設定)。
make apply 會將 manifest 複製到 server/manifest.go 和 webapp/src/manifest.ts;make manifest-check 則用於驗證它。
伺服器端
伺服器端會從 Go 模組代理導入與 Mattermost 應用程式相同的 Go 模組 github.com/mattermost/mattermost/server/public。其 main 僅負責啟動應用程式:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Plugin 類型內嵌了 plugin.MattermostPlugin,這使其擁有 p.API,並實現所需的 hooks:
type Plugin struct {
plugin.MattermostPlugin
router *mux.Router
}
func (p *Plugin) OnActivate() error {
p.router = p.newRouter()
return p.API.RegisterCommand(&model.Command{
Trigger: "weather",
AutoComplete: true,
AutoCompleteDesc: "Show the forecast",
AutoCompleteHint: "[city]",
})
}
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Forecast for " + city + ": sunny",
}, nil
}
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
}
常見 Hooks
| Hook | 執行時機 |
|---|---|
OnActivate, OnDeactivate | 當應用程式被啟用或停用,以及伺服器啟動和停止時。請在 OnActivate 中註冊指令和機器人。 |
OnConfigurationChange | 當設定變更時;使用 p.API.LoadPluginConfiguration(&cfg) 載入它們。 |
ServeHTTP | 對 /plugins/<app id>/… 的請求。當請求來自已登入的使用者時,伺服器會設定 Mattermost-User-Id 標頭;除非請求預設為公開,否則應拒絕沒有此標頭的請求。 |
ExecuteCommand | 應用程式註冊的指令被執行時。 |
MessageWillBePosted, MessageWillBeUpdated | 在訊息儲存之前:回傳修改後的貼文,或拒絕的理由。 |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | 事後執行,用於副作用。 |
ReactionHasBeenAdded, ReactionHasBeenRemoved | 反應。 |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | 成員資格和頻道變更。團隊即社群。 |
FileWillBeUploaded | 在檔案儲存之前:修改或拒絕它。 |
OnPluginClusterEvent | 來自叢集中其他伺服器上同一應用程式的訊息。 |
完整的清單及其精確簽名位於 server/public/plugin/hooks.go;API 位於 server/public/plugin/api.go。pluginapi 套件(server/public/pluginapi)以更易用的輔助函式包裝了 API。
有用的 API 呼叫
CreatePost、UpdatePost、SendEphemeralPost:以應用程式的機器人或使用者身分發布貼文。EnsureBotUser(&model.Bot{Username, DisplayName}):一次性建立應用程式的機器人並取得其 ID。PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}):向網頁應用程式發送事件。它將作為custom_<app id>_poll_updated到達。OpenInteractiveDialog:開啟對話框。LogInfo、LogError等:寫入伺服器日誌,並標記應用程式的 ID。GetUser、GetChannel、HasPermissionToChannel:在代表使用者執行操作之前,檢查誰可以執行什麼。
應用程式會執行在叢集的每台伺服器上。請將狀態儲存在金鑰-值儲存區或資料庫中,而不是只放在記憶體裡。
AI 服務
Antimatter 的伺服器會透過管理員設定的模型連線(請參閱管理員指南中的 AI)為應用程式呼叫 AI 模型。應用程式絕不持有提供者的金鑰:它只需指定代表誰、在何處執行,伺服器會檢查該使用者是否能在該處使用此連線,套用其限制與預算,將 token 計入該使用者與應用程式,並稽核呼叫內容(不含實際內容)。
| 方法 | 功能說明 |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | 聊天補全:訊息、工具、結構化輸出(JSONSchema)。 |
AIChatStream(…) | 相同功能,但採用串流方式:讀取串流的 Events 直到頻道關閉,或呼叫 Close 以停止呼叫。 |
AIEmbed(…, *ai.EmbedRequest) | 文字嵌入(Embeddings)。 |
AITranscribe(…, *ai.TranscribeRequest) | 語音轉文字。 |
AIDecide(…, *ai.DecideRequest) | 向決策模型提出有型別的問題(是或否、選擇、評分),針對特定文字。 |
AIConnectionsForUser(userID, channelID, teamID) | 使用者可在該處使用的連線,包含其模型與功能,但不包含金鑰。 |
每個請求的 ConnectionID 用於選擇連線,Model 用於選擇該連線中的一個模型(留空則使用連線的預設模型)。Operation 是用於使用記錄與稽核的簡短標籤,絕非內容。這些功能的最低伺服器版本要求為 11.11。
actingUserID:例如發送到您的 ServeHTTP 的請求中的 Mattermost-User-Id 標頭,或掛鉤上下文中的使用者(例如貼文的作者、執行指令的使用者)。切勿從訊息、Webhook 負載、URL 或其他應用程式的請求中取得:否則您的應用程式將消耗他人的存取權限。這些方法與 ai 套件(server/public/ai)僅存在於 Antimatter 的模組副本中,而非 Mattermost 發布的版本。若要使用它們,請針對 Antimatter 的 server/public 進行建置,並在您的 go.mod 中添加一行 replace,例如 replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public。此類應用程式僅能在 Antimatter 上執行。
儲存資料
每個應用程式都在伺服器的資料庫中擁有自己的金鑰-值儲存區:
KVSet(key, value)、KVGet(key)、KVDelete(key):值為位元組,通常是 JSON。金鑰最多 150 個字元。KVSetWithExpiry(key, value, seconds):值會在該時間後消失。KVCompareAndSet(key, old, new)以及帶有Atomic的KVSetWithOptions:當多個請求或伺服器寫入同一個金鑰時,可安全地更新。投票(Polls)以此方式儲存每一票,並在發生衝突時重試。KVList(page, perPage):逐頁列出金鑰。
Web 應用程式部分
套件透過 window.registerPlugin 註冊一個類別。其 initialize 方法會接收註冊表(registry)和 Redux store:
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- 共用函式庫。 Web 應用程式提供 React、ReactDOM、Redux、React Redux 和 React Intl:請將它們列為 webpack
externals(React、ReactDOM、Redux、ReactRedux、ReactIntl),而不是打包自己的副本。請參閱投票(Polls)的webpack.config.js。 - 呼叫您的伺服器部分。 使用
Client4.getOptions()(來自mattermost-redux/client)中的選項,Fetch${window.basename}/plugins/<app id>/…:這些選項包含伺服器接受來自瀏覽器請求所需的標頭。 - 您自己的狀態。
registerReducer會新增一個 reducer,儲存在state['plugins-<app id>']下。 - 型別。 Antimatter 的額外註冊表方法未包含在 Mattermost 公開的型別中:投票(Polls)在
webapp/src/types/host.ts中宣告它所使用的註冊表。
註冊表的方法及其參數在程式碼中有文件說明,位於 webapp/fusion/src/plugins/registry.ts(Fusion)和 webapp/channels/src/plugins/registry.ts(經典版)。
支援 Fusion 介面
Antimatter 有兩個 Web 介面:預設的 Fusion 和經典版。兩者載入相同的應用程式套件並擁有相同的註冊表,但 Fusion 會自行繪製許多元素,並以獨特的方式放置外掛元件。請在兩個介面中測試您的應用程式。
偵測介面
在任何應用程式載入之前,兩個介面都會將 window.antimatterWebUI 設為 'fusion' 或 'classic',並在頁面的根元素上以 data-am-web-ui 設定相同的值。在 Mattermost 上,兩者皆未設定。
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
內建應用程式使用此功能以呈現不同的視覺樣式。投票應用程式會以 Fusion 的風格繪製其投票卡片與建立對話框,並在 Fusion 中開啟自己的 /poll 對話框。通話與語音頻道會省略側邊欄與吐司通知元件,因為 Fusion 會以自己的通話介面取代這些元件。代理程式應用程式會將其 AI 動作放在訊息選單中,作為 更多動作 下的一個群組,而不是放在懸停工具列中。Fusion 自身的 CSS 類別(以 am- 為前綴)及其對話框層(am-layer 元素)是這些應用程式用來匹配介面的方式;這些並非文件化的穩定 API。
Fusion 顯示外掛元件的位置
| 註冊方法 | 在 Fusion 中的出現位置 |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | 應用程式橫列,即右側邊緣的圖示欄,位於您的頭像與關於 Antimatter按鈕之間。頻道標題按鈕也放在這裡,而非頻道標題中。當系統管理員啟用停用應用程式橫列(ExperimentalSettings.DisableAppBar,預設為停用)時,此橫列會隱藏。 |
registerRightHandSidebarComponent | 右側面板。使用者可以拖曳其邊緣來調整大小,並將其開啟在新的視窗中。如果該應用程式也是一個產品,面板會提供 以完整頁面開啟 選項。請參閱 在新視窗中的面板。 |
registerChannelHeaderIcon | 頻道標題中的圖示,位於通話按鈕之後;在手機上,則位於標題的 更多 選單中。 |
registerCallButtonAction | 頻道標題中的通話按鈕;多個提供者共用一個 其他通話方式 選單。 |
registerPostDropdownMenuAction | 訊息的 訊息動作(⋯)選單,位於其 更多動作 子選單中;也位於討論串面板的 討論串操作 選單中,位於 更多動作 之下。Fusion 會在每個動作前繪製應用程式的圖示,顏色為灰色:對於內建應用程式使用其自身的圖示,否則使用應用程式在應用程式欄中顯示的圖示,若無則使用中性的圖示。若動作文字開頭沒有文字(即應用程式自行繪製的圖示),該元素將被隱藏。 |
registerPostDropdownSubMenuAction | 在相同的 更多動作 子選單中,位於單一動作之後的一個帶有標題的群組。沒有應用程式會在訊息選單中擁有自己的獨立項目。 |
registerPostActionComponent | 訊息懸停工具列,位於 ⋯ 按鈕之前。 |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | 訊息本身:其內容、文字下方、時間之後,以及取代連結預覽的位置。 |
registerChannelHeaderMenuAction | 頻道的 頻道選項 選單(側邊欄中頻道的 ⋯,或右鍵),作為帶有插頭圖示的項目。 |
registerMainMenuAction | 社群選單(社群名稱位於頻道列表頂端),位於更多動作之下,依應用程式分組。 |
registerSidebarBrowseOrAddChannelMenuAction | 社群選單,位於 建立頻道 與 建立分類 之後。 |
registerFileUploadMethod | 輸入框的 + 選單(新增至訊息),位於 上傳檔案 之後。 |
registerPostEditorActionComponent | 輸入框的格式工具列,當顯示格式選項時。 |
registerAIActionMenuItemComponent | 輸入框的 AI 選單。 |
registerFileDropdownMenuAction, registerFilePreviewComponent | 檔案的選項選單,以及檔案預覽。 |
registerUserSettings | 個人檔案與設定,在擴充程式標題下,每個應用程式各有一個分頁。 |
registerChannelSettingsTab | 頻道設定對話框,位於 應用程式 標題下。 |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | 點擊名稱或頭像時顯示的個人檔案卡片。 |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | 社群頻道清單的頂端、頻道名稱旁邊、頻道圖示,以及左側社群欄的底部。 |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | 頻道歷史記錄的開始、訊息清單的頂端、未讀線、輸入框上方,以及輸入框的佔位文字。 |
registerChannelTypeOption | 建立頻道對話框中的頻道類型。 |
registerSearchComponents | 頻道標題列中的搜尋框:當其為空時,會顯示應用程式範圍。 |
registerUserGuideDropdownMenuAction | 關於 Antimatter(應用程式欄底部),位於 說明 之下。 |
registerProduct | 位於產品 URL 的完整頁面,列於 關於 Antimatter 中的 產品 之下。產品頁面保留經典的頁面框架,其產品選單僅在應用程式註冊產品或向其新增項目時才會顯示。內建的看板與操作手冊應用程式不再註冊產品:其頁面會在側邊面板中開啟。 |
registerRootComponent、registerGlobalComponent,以及 registerMessageWillBePostedHook、registerSlashCommandWillBePostedHook、registerMessageWillFormatHook、registerFilesWillUploadHook、registerDesktopNotificationHook、registerWebSocketEventHandler、registerReducer、registerTranslations 等掛鉤。 | 運作方式與經典介面相同。 |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | 系統控制台,在兩種介面中皆為經典版本。 |
Fusion 不顯示或不執行的項目
registerPostDropdownMenuComponent(訊息選單中的元件)、registerPostCardTypeComponent(右側面板中的貼文卡片)以及registerProductSwitcherMenuItem不會顯示在 Fusion 的主視圖中。請改用registerPostDropdownMenuAction取代前者。- 當在 Fusion 中編輯訊息時,
registerMessageWillBeUpdatedHook不會執行。請改用MessageWillBeUpdated掛鉤在伺服器端檢查編輯。 registerActionAfterChannelCreation僅從經典頻道建立對話框執行,Fusion 會從其 建立頻道 對話框中的 更多選項… 開啟該對話框。- 無論
supportedProductIds為何,應用程式列元件都會顯示。 - 來自
registerNeedsTeamRoute和registerCustomRoute的路由保留經典頁面框架,與產品相同。
僅 Antimatter 擁有的掛鉤
這些註冊方法在 Mattermost 中不存在。呼叫前請確認方法存在,以便應用程式也能在那裡運作:
if (typeof registry.registerComposerMenuItem === 'function') {
registry.registerComposerMenuItem({
text: 'Create a poll',
icon: <i className='icon icon-chart-bar'/>,
fusionIcon: 'poll',
action: ({channelId, rootId}) => openPollCreation(channelId, rootId),
});
} else {
registry.registerChannelHeaderButtonAction(icon, (channel) => openPollCreation(channel.id), 'Create a poll', 'Create a poll');
}
| 方法 | 新增的內容 |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | 在兩種介面中,訊息框 + 選單中的一個項目。action 接收 {channelId, rootId}。 |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | 訊息框表情符號選擇器的一個分頁,例如 GIF 或貼紙。在 Fusion 中,order 低於 10 的分頁會排在表情符號分頁之前。該元件接收 channelId、rootId、filter、onFilterChange、onSelectDone、insertText、isFusion,以及在 Fusion 中的 pickerKit(見下文)。 |
registerChannelViewPanel({matcher, component}) | 位於頻道標題與訊息之間的面板,用於 matcher(state, channel) 所選取的頻道。它可以隱藏訊息與輸入框(setMessagesVisible),就像語音頻道那樣。 |
registerSidebarChannelFooterComponent(component) | 側邊欄中頻道下方的一行,例如顯示該頻道通話中的人員。它會接收 channel。 |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | 僅限 Fusion:在整合頁面上,自訂表情符號旁邊的項目。GIFs 應用程式會將其自訂 GIF 和貼紙新增到該處。 |
在表情符號選擇器中新增及管理檢視
在 Fusion 中,表情符號選擇器分頁會收到一個 pickerKit 屬性:這是表情符號分頁用於其新增與管理檢視的元件,讓分頁的檢視保持一致並保持在選擇器內。GIFs 和 Memes 應用程式使用它來新增和管理 GIF、貼紙和迷因模板。它包含 version (1)、鉤子 usePickerView 和 useOnEscape,以及元件 PickerViewButtons(搜尋框旁邊的 + 和管理按鈕)、PickerViewFrame(帶有返回箭頭的檢視)、PickerNotice、PickerField、PickerSearch、PickerDropZone 和 PickerManageList。按 Esc 鍵會從檢視返回到分頁的網格。請確認該屬性存在:其他宿主會省略它,此時分頁會保留自己的對話框。類型定義位於 webapp/fusion/src/types/store/plugins.ts(EmojiPickerTabProps),元件位於 webapp/fusion/src/fusion/popovers/picker_views.tsx。
在新視窗中的面板
Fusion 會為 registerRightHandSidebarComponent 的元件提供另外兩個屬性,以便 在新視窗中開啟 能在面板原本的位置開啟,而不是在應用程式首頁:
setPanelState(state):當面板位置變更時呼叫,並傳入一個由您的應用程式定義的短字串,例如'note/<id>'或'board/<id>/card/<id>',或在首頁時傳入null。它會進入視窗網址,因此切勿在其中放入機密資訊。長度超過 512 個字元或包含控制字元的狀態會被捨棄。initialPanelState:在新視窗中,這是面板之前的狀態,或undefined。請將其視為不可信的輸入:請以登入者的身分重新取得該項目,如果他們無法開啟,則顯示應用程式首頁或找不到檢視。
當面板關閉或另一個應用程式的面板取代它時,狀態會被遺忘。若要自行開啟面板視窗,popoutRhsPlugin(也位於 window.WebappUtils.popouts)接受狀態作為可選的第五個參數。筆記、白板、待辦事項、看板和操作手冊使用此功能。
fusionIcon 指定 Fusion 圖示集中的某個圖示(例如 poll、sticker、calendar 或 image;請參閱 webapp/fusion/src/fusion/components/icon)。Fusion 會顯示此圖示而非 icon,以確保該項目與其他項目保持一致。
設定
plugin.json中的settings_schema描述了該應用程式在系統控制台 › 擴充程式中的頁面:
"settings_schema": {
"header": "Forecasts come from the weather service below.",
"settings": [
{"key": "ServiceURL", "display_name": "Service URL", "type": "text", "default": "https://api.example.com"},
{"key": "Units", "display_name": "Units", "type": "dropdown", "default": "metric",
"options": [{"display_name": "Celsius", "value": "metric"}, {"display_name": "Fahrenheit", "value": "imperial"}]},
{"key": "EnableAlerts", "display_name": "Severe weather alerts", "type": "bool", "default": false,
"help_text": "Post alerts in the channels that follow a city."}
]
}
設定類型包括:bool、text、longtext、number、dropdown、radio、generated(隨機密鑰)、username 以及 custom(由 Web 應用程式部分透過 registerAdminConsoleCustomSetting 註冊的元件繪製)。設定可以分組到 sections 中。
伺服器端會將其讀取到一個結構體中,其欄位與金鑰相符:
type configuration struct {
ServiceURL string
Units string
EnableAlerts bool
}
func (p *Plugin) OnConfigurationChange() error {
var cfg configuration
if err := p.API.LoadPluginConfiguration(&cfg); err != nil {
return err
}
p.setConfiguration(&cfg)
return nil
}
管理員也可以在設定中設定這些項目,位於 PluginSettings.Plugins.<app id> 之下,金鑰需使用小寫(請參閱管理員指南)。
翻譯
Antimatter 已翻譯成 37 種語言,內建應用程式也是如此。Web 應用程式部分透過以下方式進行翻譯:
- 使用 React Intl 撰寫所有字串,並包含 ID 及其英文文字:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>或defineMessages。請使用您的應用程式名稱作為 ID 的前綴。 - 將英文字串提取到
webapp/i18n/en.json(內建應用程式使用formatjs extract)。 - 在其旁邊為每種語言添加一個檔案,使用相同的 ID:
fr.json、de.json、pt-BR.json、zh-CN.json… - 將它們交給 Web 應用程式,該應用程式會詢問使用者的語言:
import fr from '../i18n/fr.json'; import ptBR from '../i18n/pt-BR.json'; const catalogs = {fr, 'pt-BR': ptBR}; export function translationsFor(locale) { return catalogs[locale] || catalogs[locale.split('-')[0]] || {}; } registry.registerTranslations(translationsFor);
如果找不到字串,系統會回退到英文的 defaultMessage。對於由伺服器端傳送的字串(例如指令回應、機器人訊息),pluginapi/i18n 套件會載入隨 bundle 提供的翻譯檔案(例如 assets/i18n),並為每位使用者提供對應語言的 localizer。
建置、安裝與除錯
您需要 Go 和 Node.js(版本請參照應用程式的 .nvmrc)。在應用程式的資料夾中執行:
| 指令 | 功能說明 |
|---|---|
make dist | 為所有平台建置伺服器端,並建置 Web 應用程式端,然後將 bundle 寫入 dist/<app id>-<version>.tar.gz。 |
make test | 執行 Go 和 JavaScript 測試。 |
make check-style | 檢查程式碼(golangci-lint、ESLint)並驗證 TypeScript 型別。 |
make deploy | 建置後,在伺服器上安裝並啟用該 bundle。 |
make watch, make deploy-from-watch | 第一個指令會在您每次儲存時重新建置 Web 應用程式端;第二個指令則會將建置結果打包並安裝到伺服器上。 |
make enable, make disable, make reset | 在伺服器上啟用、停用或重新啟動應用程式。 |
make logs, make logs-watch | 顯示伺服器記錄中屬於該應用程式的項目。 |
make attach | 將 Delve 除錯器附加到正在執行的伺服器端(請先使用 AM_DEBUG=1 進行建置)。 |
make deploy 和其他伺服器指令在找到本地模式 socket 時會使用它(/var/tmp/antimatter_local.socket,或 AM_LOCALSOCKETPATH):請在伺服器機器上開啟本地模式並執行這些指令。否則,它們會使用 AM_ADMIN_TOKEN,或 AM_ADMIN_USERNAME 和 AM_ADMIN_PASSWORD 登入由 MM_SERVICESETTINGS_SITEURL 指定的伺服器。建置工具目前尚未讀取用於網站 URL 的 AM_ 變數。
手動安裝 bundle
- 系統管理員可透過
AM_PLUGINSETTINGS_ENABLEUPLOADS=true允許上傳(預設為關閉;系統控制台中沒有對應的開關)。 - 接著前往系統控制台 › 擴充程式 › 擴充程式管理 › 上傳擴充程式,或執行
amctl plugin add weather.tar.gz,然後執行amctl plugin enable com.example.weather。 - 如果伺服器啟用了擴充程式需經簽署,它只接受使用其信任金鑰簽署的套件;請參閱安裝其他應用程式。
AM_SERVICESETTINGS_ENABLEDEVELOPER=true:這樣錯誤訊息就會包含詳細資訊。當您的 shell 中設定了 MM_SERVICESETTINGS_ENABLEDEVELOPER 時,隨附應用程式的 Makefile 只會為您的平台建置伺服器端。