Skriva appar (plugins)
Appar lägger till funktioner i Antimatter inifrån: en serverdel i Go som kopplar in sig i servern, och en webappdel i React som lägger till menyer, paneler, meddelandetyper och sidor. Denna sida visar hur en app är uppbyggd, med den medföljande appen Omröstningar som exempel, och vad som ändras i Antimatters Fusion-gränssnitt.
Så här fungerar en app
- Serverdelen är ett Go-program. Servern startar det som en separat process och kommunicerar med det via RPC: den anropar appens hooks (ett meddelande ska publiceras, ett kommando körs …) och appen anropar serverns plugin API (skapa ett inlägg, läsa en användare, lagra data …). Den kan också servera HTTP-rutter under
/plugins/<app id>/. - Webappdelen är en JavaScript-bundle som laddas in i webbläsaren tillsammans med webappen. Den registrerar React-komponenter och callback-funktioner i registry: en knapp i en meny, en panel, ett sätt att rita ut en meddelandetype.
- Varje del är valfri. En app installeras som en
.tar.gz-bundle, och systemadministratörer aktiverar, inaktiverar och konfigurerar den.
Antimatter kör appar som är skrivna för Mattermost, och tvärtom, så länge en app endast använder hooks som båda har. Se Hooks som endast Antimatter har.
Bundle-layout
Det finns inget separat startmall: börja med en liten medföljande app som antimatter-plugin-polls eller antimatter-plugin-todo, och ändra dess ID och kod. Omröstningar är uppbyggd så här:
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
Valfritt: en public/-mapp, som serveras som den är på /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: unik och permanent; ett omvänt domännamn är vanligt. Det namnger rutter, de lagrade inställningarna och WebSocket-händelserna.version: semantisk version. De medföljande apparna utesluter den frånplugin.json; deras byggverktyg hämtar den från den senastev*-git-taggen.min_server_version: Antimatter rapporterar12.0.0.settings_schema: appens inställningssida i Systemkonsolen (se Inställningar).
make apply kopierar manifestet till server/manifest.go och webapp/src/manifest.ts; make manifest-check validerar det.
Serverdelen
Serverdelen importerar samma Go-modul som Mattermost-appar, github.com/mattermost/mattermost/server/public, från Go-modulproxyn. Dess main startar bara appen:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Typen Plugin bäddar in plugin.MattermostPlugin, vilket ger den p.API, och implementerar de hooks den behöver:
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)
}
Vanliga hooks
| Hook | När den körs |
|---|---|
OnActivate, OnDeactivate | När appen aktiveras eller inaktiveras, samt när servern startar och stängs av. Registrera kommandon och botar i OnActivate. |
OnConfigurationChange | När dess inställningar ändras; ladda dem med p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Begäranden till /plugins/<app id>/…. Servern sätter Mattermost-User-Id-rubriken när begärandet kommer från en inloggad användare; neka begäranden utan den om de inte är avsedda att vara publika. |
ExecuteCommand | När ett kommando som appen registrerat körs. |
MessageWillBePosted, MessageWillBeUpdated | Innan ett meddelande sparas: returnera ett ändrat inlägg eller en anledning att neka det. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | I efterhand, för biverkningar. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Reaktioner. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Ändringar av medlemskap och kanaler. Ett team är en gemenskap. |
FileWillBeUploaded | Innan en fil lagras: ändra den eller neka den. |
OnPluginClusterEvent | Ett meddelande från samma app på en annan server i en kluster. |
Den fullständiga listan, med exakta signaturer, finns i server/public/plugin/hooks.go; API:t finns i server/public/plugin/api.go. Paketet pluginapi (server/public/pluginapi) kapslar in API:t i vänligare hjälpfunktioner.
Användbara API-anrop
CreatePost,UpdatePost,SendEphemeralPost: inlägga som appens bot eller som en användare.EnsureBotUser(&model.Bot{Username, DisplayName}): skapa appens bot en gång och få dess ID.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): skicka ett händelse till webbappen. Den kommer in somcustom_<app id>_poll_updated.OpenInteractiveDialog: öppna en dialogruta.LogInfo,LogErroroch vänner: skriva till serverlogg, taggat med appens ID.GetUser,GetChannel,HasPermissionToChannel: kontrollera vem som får göra vad innan du agerar för en användare.
En app körs på varje server i en kluster. Spara tillstånd i nyckel-värde-lagret eller i databasen, inte bara i minnet.
AI-tjänsten
Antimatters server anropar AI-modeller för appar via de modellanslutningar som en administratör har konfigurerat (se AI i administratörguiden). En app förvarar aldrig en leverantörs nyckel: den anger vilken person den agerar för och var, och servern kontrollerar att personen får använda anslutningen där, tillämpar dess begränsningar och budgetar, räknar token mot personen och appen, samt granskar anropet utan dess innehåll.
| Metod | Vad den gör |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | En chattfullbordan: meddelanden, verktyg, strukturerad output (JSONSchema). |
AIChatStream(…) | Samma, strömmat: läs strömmens Events tills kanalen stängs, eller Close den för att stoppa anropet. |
AIEmbed(…, *ai.EmbedRequest) | Embeddings av texter. |
AITranscribe(…, *ai.TranscribeRequest) | Tal till text. |
AIDecide(…, *ai.DecideRequest) | Typade frågor till ett beslutsmodell (ja eller nej, ett val, ett betyg) om en text. |
AIConnectionsForUser(userID, channelID, teamID) | De anslutningar en person får använda där, med deras modeller och funktioner, aldrig deras nycklar. |
Varje anropets ConnectionID väljer anslutningen, och Model en av dess modeller (tomt för anslutningens standard). Operation är en kort etikett för användnings- och granskningsregister, aldrig innehåll. Deras minsta serverversion är 11.11.
actingUserID endast från en källa som servern verifierat: Mattermost-User-Id-rubriken i ett anrop till din ServeHTTP, eller användaren i en hooks kontext (ett inläggs författare, användaren som körde ett kommando). Ta aldrig ifrån ett meddelande, en webhook-payload, en URL eller en annan apps anrop: din app skulle annars använda någon annans åtkomst.Dessa metoder och ai-paketet (server/public/ai) finns endast i Antimatters kopia av modulen, inte i versionen publicerad av Mattermost. För att använda dem, bygg mot Antimatters server/public med en replace-rad i din go.mod, till exempel replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. En sådan app körs endast på Antimatter.
Lagring av data
Varje app har sitt eget nyckel-värde-lager i serverns databas:
KVSet(key, value),KVGet(key),KVDelete(key): värden är byte, vanligtvis JSON. Nycklar är högst 150 tecken.KVSetWithExpiry(key, value, seconds): värdet försvinner efter den tiden.KVCompareAndSet(key, old, new)ochKVSetWithOptionsmedAtomic: säkra uppdateringar när flera begäranden eller servrar skriver samma nyckel. Polls sparar varje röst på detta sätt och gör om vid konflikt.KVList(page, perPage): nycklarna, sida för sida.
Webbapp-delen
Bundlen registrerar en klass med window.registerPlugin. Dess initialize tar emot registret och Redux-lagret:
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());
- Delade bibliotek. Webbappen tillhandahåller React, ReactDOM, Redux, React Redux och React Intl: ange dem som webpack
externals(React,ReactDOM,Redux,ReactRedux,ReactIntl) istället för att bundla egna kopior. Se Polls'webpack.config.js. - Anrop av din serverdel. Hämta
${window.basename}/plugins/<app id>/…med alternativen frånClient4.getOptions()(frånmattermost-redux/client): de bär med sig de huvuden som servern behöver för att acceptera en begäran från webbläsaren. - Ditt eget tillstånd.
registerReducerlägger till en reducer, som sparas understate['plugins-<app id>']. - Typen. Antimatters extra registreringsmetoder finns inte i Mattermosts publicerade typer: Polls deklarerar det register det använder i
webapp/src/types/host.ts.
Registrets metoder och deras parametrar är dokumenterade i koden, i webapp/fusion/src/plugins/registry.ts (Fusion) och webapp/channels/src/plugins/registry.ts (klassisk).
Stöd för Fusion-gränssnittet
Antimatter har två webbgränssnitt: Fusion, som är standard, och det klassiska. Båda laddar samma appbundlar och har samma register, men Fusion ritar många saker själv och placerar komponenter från appar på sitt eget sätt. Testa din app i båda.
Identifiera gränssnittet
Innan någon app laddas, sätter båda gränssnitten window.antimatterWebUI till 'fusion' eller 'classic', och samma värde på sidans rot-element som data-am-web-ui. I Mattermost är varken satt.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
De medföljande apparna använder detta för att renderas annorlunda. Omröstningar ritar sina kort och sitt skapelsedialog i Fusion-stil och öppnar ett eget dialog för /poll i Fusion. Samtal och röstkanaler utesluter sidofältet och toast-komponenterna som Fusion byter ut mot sitt eget samtalsgränssnitt. Agent-appen placerar sina AI-åtgärder i meddelandemenyn, som en grupp under Fler åtgärder, istället för förhållningsverktygsfältet. Fusions egna CSS-klasser (med prefixet am-) och dess dialoglager (elementet am-layer) är det som dessa appar använder för att matcha gränssnittet; de är inte en dokumenterad, stabil API.
Var Fusion visar plugin-komponenter
| Registreringsmetod | Var det visas i Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | Appspåret, kolumnen med ikoner på höger kant, mellan din avatar och knappen Om Antimatter. Kanalrubrikknappar hamnar också där, inte i kanalrubriken. Dold när en systemadministratör aktiverar Inaktivera Apps Bar (ExperimentalSettings.DisableAppBar, inaktiverad som standard). |
registerRightHandSidebarComponent | Panelen till höger. Användare kan ändra dess storlek genom att dra i kanten och öppna den i ett nytt fönster. Om appen också är en produkt erbjuder panelen Öppna som helsida. Se Panelen i ett nytt fönster. |
registerChannelHeaderIcon | Ikoner i kanalens huvud, efter samtalssknapparna; på telefoner, i huvudets Mer-meny. |
registerCallButtonAction | Kanalens huvud samtalssknapp; flera leverantörer delar en Andra sätt att ringa-meny. |
registerPostDropdownMenuAction | Meddelandets Åtgärder för meddelanden (⋯) -meny, i dess undermeny Fler åtgärder; även i trädpanelens Trådåtgärder -meny, under Fler åtgärder. Fusion ritar appens ikon före varje åtgärd, i grått: dess egen ikon för de medföljande apparna, annars den ikon appen visar i appspåret, annars en neutral. Ett element utan text i början av åtgärdens text (en ikon appen ritar själv) göms. |
registerPostDropdownSubMenuAction | En grupp med en rubrik i samma undermeny Fler åtgärder, efter de enskilda åtgärderna. Ingen app får ett eget objekt i meddelandemenyn. |
registerPostActionComponent | Meddelandets förhandsvisningsverktygsfält, före ⋯-knappen. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | Självständigt meddelande: dess innehåll, under dess text, efter dess tid, och i stället för länkförhandsvisningar. |
registerChannelHeaderMenuAction | Kanalens Kanalalternativ -meny (⋯ på en kanal i sidofältet, eller högerklick), som objekt med en stickkontakt-ikon. |
registerMainMenuAction | Gemenskapens meny (gemenskapens namn i toppen av kanallistan), under Fler åtgärder, grupperad per app. |
registerSidebarBrowseOrAddChannelMenuAction | Gemenskapens meny, efter Skapa kanal och Skapa kategori. |
registerFileUploadMethod | Komponerarens + -meny (Lägg till i meddelandet), efter Ladda upp en fil. |
registerPostEditorActionComponent | Skrivfältets formateringsfält, när formatering visas. |
registerAIActionMenuItemComponent | Skrivfältets AI-meny. |
registerFileDropdownMenuAction, registerFilePreviewComponent | En filens inställningsmeny, och filens förhandsvisning. |
registerUserSettings | Profil & inställningar, en flik per app under rubriken Plugins. |
registerChannelSettingsTab | Kanalinställningssammanhanget, under rubriken Appar. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | Profilkortet som visas när du klickar på ett namn eller en avatar. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | Överst i kanallistan för en gemenskap, bredvid kanalens namn, kanalens ikon och längst ner i gemenskapslistan till vänster. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | Början av kanalens historik, överst i meddelandelistan, olästlinjen, ovanför meddelandefältet och meddelandefältets platshållare. |
registerChannelTypeOption | Kanaltyperna i dialogrutan Skapa kanal. |
registerSearchComponents | Sökfältet i kanalens sidhuvud: appomfång visas när det är tomt. |
registerUserGuideDropdownMenuAction | Om Antimatter (längst ner i appmenyn), under Hjälp. |
registerProduct | Helsidor på produktens URL, listade under Produkter i Om Antimatter. Produktsidor behåller den klassiska sidramen, vars produktmeny endast visas när en app registrerar en produkt eller lägger till ett objekt i den. De medföljande apparna Kanban och Playbooks registrerar inte längre produkter: deras sidor öppnas i deras sidopaneler. |
registerRootComponent, registerGlobalComponent, hooks såsom registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Fungerar som i det klassiska gränssnittet. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | Systemkonsolen, som är den klassiska i båda gränssnitten. |
Det Fusion inte visar eller kör
registerPostDropdownMenuComponent(en komponent i meddelandemenyn),registerPostCardTypeComponent(ett meddelandekort i panelen till höger) ochregisterProductSwitcherMenuItemvisas inte i Fusions huvudvyer. AnvändregisterPostDropdownMenuActionistället för den första.registerMessageWillBeUpdatedHookkörs inte när ett meddelande redigeras i Fusion. Kontrollera redigeringar på servern med hookenMessageWillBeUpdatedistället.registerActionAfterChannelCreationkörs endast från den klassiska dialogrutan för kanalskapning, som Fusion öppnar från Fler alternativ… i dialogrutan Skapa kanal.- Komponenter i appmenyn visas oavsett deras
supportedProductIds. - Sökvägar från
registerNeedsTeamRouteochregisterCustomRoutebehåller den klassiska sidramen, liksom produkter.
Hooks som endast Antimatter har
Dessa registreringsmetoder finns inte i Mattermost. Kontrollera att en metod finns innan du anropar den, så att appen också fungerar där:
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');
}
| Metod | Vad den lägger till |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | En post i meddelandefältets +-meny, i båda gränssnitten. action tar emot {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | En flik i meddelandefältets emojivaljare, till exempel GIF:er eller klistermärken. I Fusion kommer flikar med ett order-värde under 10 före fliken Emojier. Komponenten tar emot channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion och, i Fusion, pickerKit (se nedan). |
registerChannelViewPanel({matcher, component}) | En panel mellan kanalens rubrik och meddelandena, för de kanaler som matcher(state, channel) väljer. Den kan dölja meddelandena och meddelandefältet (setMessagesVisible), precis som röstkanaler gör. |
registerSidebarChannelFooterComponent(component) | En rad under en kanal i sidofältet, till exempel personerna i dess samtal. Den tar emot channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Endast Fusion: en post på sidorna Integrationer, intill anpassade emojier. GIF-appen lägger till sina anpassade GIF:er och klistermärken där. |
Lägg till och hantera vyer i emojivaljaren
I Fusion får en flik i emojivaljaren egenskapen pickerKit: de komponenter som fliken Emojier använder för sina egna vyer för att lägga till och hantera, så att en fliks vyer ser likadana ut och förblir inuti valjaren. Apparna GIF:er och Memes använder den för att lägga till och hantera GIF:er, klistermärken och memmallar. Den innehåller version (1), hookarna usePickerView och useOnEscape, samt komponenterna PickerViewButtons (knapparna + och Hantera bredvid sökfältet), PickerViewFrame (en vy med dess bak-pekare), PickerNotice, PickerField, PickerSearch, PickerDropZone och PickerManageList. Escape går tillbaka från en vy till flikens rutnät. Kontrollera att egenskapen finns: andra värdar utesluter den, och då behåller fliken sina egna dialogrutor. Typerna finns i webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) och komponenterna i webapp/fusion/src/fusion/popovers/picker_views.tsx.
Panelen i ett nytt fönster
Fusion ger komponenten för registerRightHandSidebarComponent två ytterligare egenskaper, så att Öppna i nytt fönster öppnar panelen där den var istället för på appens startsida:
setPanelState(state): anropa den när panelen byter plats, med en kort sträng som din app definierar, till exempel'note/<id>'eller'board/<id>/card/<id>', ellernullpå dess startsida. Den hamnar i fönstrets adress, så lägg aldrig in en hemlighet i den. En tillstånd som är längre än 512 tecken, eller som innehåller styrtecken, kasseras.initialPanelState: i det nya fönstret, det tillstånd som panelen hade, ellerundefined. Behandla det som opålitlig indata: hämta objektet igen som den inloggade användaren, och visa appens startsida eller en vy för att objektet inte hittades om de inte kan öppna det.
Tillståndet glöms bort när panelen stängs eller när en annan apps panel tar dess plats. För att själv öppna ett panels fönstert, popoutRhsPlugin (även i window.WebappUtils.popouts) tar tillståndet som ett valfritt femte argument. Anteckningar, Whiteboard, Att göra, Kanban och Playbooks använder detta.
fusionIcon anger en ikon ur Fusions ikonuppsättning (till exempel poll, sticker, calendar eller image; se webapp/fusion/src/fusion/components/icon). Fusion visar den istället för icon, så att objektet matchar de andra.
Inställningar
settings_schema i plugin.json beskriver appens sida i Systemkonsol › Plugins:
"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."}
]
}
Inställningstyper: bool, text, longtext, number, dropdown, radio, generated (en slumpmässig hemlighet), username och custom (ritad av en komponent som webbapplikationsdelen registrerar med registerAdminConsoleCustomSetting). Inställningar kan grupperas i sections.
Serverdelen läser in dem i en struktur vars fält matchar nycklarna:
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
}
Administratörer kan också ställa in dem i konfigurationen, under PluginSettings.Plugins.<app id>, med nycklar i gemina bokstäver (se administratörguiden).
Översättningar
Antimatter är översatt till 37 språk, och medföljande appar är det också. Webbapplikationsdelen gör det så här:
- Skriv varje sträng med React Intl, med en ID och dess engelska text:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>ellerdefineMessages. Lägg appens namn som prefix på ID:erna. - Extrahera de engelska strängarna till
webapp/i18n/en.json(medföljande appar använderformatjs extract). - Lägg till en fil per språk intill den, med samma ID:er:
fr.json,de.json,pt-BR.json,zh-CN.json… - Ge dem till webbappen, som frågar efter användarens språk:
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);
En saknad sträng faller tillbaka på sin engelska defaultMessage. För strängar som serverdelen skickar (kommandosvar, botmeddelanden) läser paketet pluginapi/i18n in översättningsfiler som medföljer i bunlen (till exempel assets/i18n) och ger en lokaliserare på varje användares språk.
Bygg, installera och felsök
Du behöver Go och Node.js (versionen i appens .nvmrc). I appens mapp:
| Kommando | Vad den gör |
|---|---|
make dist | Bygger serverdelen för alla plattformar och webbapplikationsdelen, och skriver bunlen till dist/<app id>-<version>.tar.gz. |
make test | Kör Go- och JavaScript-testerna. |
make check-style | Linterar koden (golangci-lint, ESLint) och kontrollerar TypeScript-typerna. |
make deploy | Bygger, installerar och aktiverar sedan bunlen på en server. |
make watch, make deploy-from-watch | Den första bygger om webbapplikationsdelen varje gång du sparar; den andra bunlar det den byggt och installerar det på servern. |
make enable, make disable, make reset | Aktivera, inaktivera eller starta om appen på servern. |
make logs, make logs-watch | Visa appens rader i serverloggen. |
make attach | Fäster Delve-felsökaren vid den körande serverdelen (bygg först med AM_DEBUG=1). |
make deploy och de andra serverkommandona använder en lokal modus-socket när de hittar en (/var/tmp/antimatter_local.socket, eller AM_LOCALSOCKETPATH): kör dem på serverns maskin med lokal modus på. Annars loggar de in på servern som anges av MM_SERVICESETTINGS_SITEURL med AM_ADMIN_TOKEN, eller AM_ADMIN_USERNAME och AM_ADMIN_PASSWORD. Byggnadsverktygen läser inte ännu ett AM_-namn för server-URL:en.
Installera en bunl manuellt
- En systemadministratör tillåter uppladdningar med
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(av som standard; det finns ingen växlar i Systemkonsolen). - Därefter Systemkonsol › Plugin › Pluginadministration › Ladda upp plugin, eller
amctl plugin add weather.tar.gzföljt avamctl plugin enable com.example.weather. - Om servern har Kräv Plugin-signatur aktiverat, accepterar den endast paket som är signerade med en nyckel den litar på; se Installation av andra appar.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true på servern: felmeddelanden bär då med sig sina detaljer. De medföljande apparnas Makefiles bygger serverdelen endast för din plattform när MM_SERVICESETTINGS_ENABLEDEVELOPER är satt i din skal.