Plugins handle the transformation between external file formats (JSON, i18next, XLIFF) and inlang's internal data model. They only do import/export — they don't touch the database directly.
┌─────────────────┐ ┌─────────┐ ┌──────────────────┐
│ .inlang file │◄─────►│ Plugins │◄─────►│ Translation files│
│ (SQLite) │ │ │ │ (JSON, XLIFF) │
└─────────────────┘ └─────────┘ └──────────────────┘
Plugin interface
type InlangPlugin<ExternalSettings = unknown> = {
key: string;
settingsSchema?: TObject;
toBeImportedFiles?: (args) => Promise<Array<{ path, locale, metadata? }>>;
importFiles?: (args) => Promise<{ bundles, messages, variants }>;
exportFiles?: (args) => Promise<Array<ExportFile>>;
meta?: Record<string, Record<string, unknown>>;
};
Minimal example
import type { InlangPlugin } from "@inlang/sdk";
export const plugin: InlangPlugin = {
key: "plugin.my.json",
toBeImportedFiles: async ({ settings }) => {
return settings.locales.map((locale) => ({
path: `./messages/${locale}.json`,
locale,
}));
},
importFiles: async ({ files, settings }) => {
const bundles = [];
const messages = [];
const variants = [];
for (const file of files) {
const json = JSON.parse(new TextDecoder().decode(file.content));
for (const [key, value] of Object.entries(json)) {
bundles.push({ id: key, declarations: [] });
messages.push({
bundleId: key,
locale: file.locale,
selectors: [],
});
variants.push({
messageBundleId: key,
messageLocale: file.locale,
matches: [],
pattern: [{ type: "text", value: value as string }],
});
}
}
return { bundles, messages, variants };
},
exportFiles: async ({ bundles, messages, variants, settings }) => {
const files: Record<string, Record<string, string>> = {};
for (const message of messages) {
const variant = variants.find((v) => v.messageId === message.id);
const text = variant?.pattern
.filter((p) => p.type === "text")
.map((p) => p.value)
.join("");
if (!files[message.locale]) files[message.locale] = {};
files[message.locale][message.bundleId] = text ?? "";
}
return Object.entries(files).map(([locale, content]) => ({
locale,
name: `${locale}.json`,
content: new TextEncoder().encode(JSON.stringify(content, null, 2)),
}));
},
};
Methods
toBeImportedFiles
Discovers which files should be imported from the filesystem.
toBeImportedFiles: async ({ settings }) => {
return [
{ path: "./messages/en.json", locale: "en" },
{ path: "./messages/de.json", locale: "de" },
];
}
Parameters:
settings— Project settings including plugin-specific config
Returns: Array of file descriptors:
path— Path to the filelocale— Locale this file containsmetadata— Optional, passed toimportFiles
importFiles
Parses file content and converts to inlang's data model.
importFiles: async ({ files, settings }) => {
return {
bundles: [...],
messages: [...],
variants: [...],
};
}
Parameters:
files— Array of files to import:locale— The localecontent— Binary file content (Uint8Array)toBeImportedFilesMetadata— Metadata fromtoBeImportedFiles
settings— Project settings
Returns:
bundles— Array ofBundleImportmessages— Array ofMessageImportvariants— Array ofVariantImport
exportFiles
Converts inlang's data model back to files.
exportFiles: async ({ bundles, messages, variants, settings }) => {
return [
{
locale: "en",
name: "en.json",
content: new TextEncoder().encode(JSON.stringify(data)),
},
];
}
Parameters:
bundles— All bundlesmessages— All messagesvariants— All variantssettings— Project settings
Returns: Array of files to write:
locale— The localename— Filename (e.g.,"en.json")content— Binary content (Uint8Array)
Settings schema
Define plugin settings using TypeBox:
import { Type } from "@sinclair/typebox";
export const PluginSettings = Type.Object({
pathPattern: Type.String({
pattern: ".*\\{locale\\}.*\\.json$",
description: "Path to translation files",
examples: ["./messages/{locale}.json"],
}),
sort: Type.Optional(
Type.Union([Type.Literal("asc"), Type.Literal("desc")])
),
});
Then reference it in your plugin:
export const plugin: InlangPlugin<{
"plugin.my.json": typeof PluginSettings;
}> = {
key: "plugin.my.json",
settingsSchema: PluginSettings,
// ...
};
Users configure your plugin in settings.json:
{
"baseLocale": "en",
"locales": ["en", "de"],
"modules": ["./plugins/my-plugin.js"],
"plugin.my.json": {
"pathPattern": "./messages/{locale}.json",
"sort": "asc"
}
}
Import types
BundleImport
type BundleImport = {
id: string;
declarations: Declaration[];
};
MessageImport
type MessageImport = {
id?: string; // auto-generated if omitted
bundleId: string;
locale: string;
selectors: VariableReference[];
};
VariantImport
Variants can reference messages by ID or by bundle/locale:
// By message ID
type VariantImport = {
id?: string;
messageId: string;
matches: Match[];
pattern: Pattern;
};
// By bundle/locale (recommended)
type VariantImport = {
messageBundleId: string;
messageLocale: string;
matches: Match[];
pattern: Pattern;
};
Meta
Use meta to expose plugin-specific APIs to other tools:
export const plugin: InlangPlugin = {
key: "plugin.my.json",
meta: {
"app.inlang.ideExtension": {
documentSelectors: [{ language: "json" }],
},
},
};
Next steps
- Writing a Plugin — Step-by-step guide to building a plugin
- Data Model — Understand bundles, messages, and variants
- Architecture — See how plugins fit in the architecture