Plugins handle the transformation between external file formats (JSON, i18next, XLIFF) and inlang's internal data model. .inlang is the canonical open format for localization; external translation files are compatibility files. Plugins only do import/export — they don't write the .inlang project directly.
┌─────────────────┐ ┌─────────┐ ┌──────────────────┐
│ .inlang file │◄─────►│ Plugins │◄─────►│ Translation files│
│ │ │ │ │ (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.
Use messageBundleId plus messageLocale for most importFiles() implementations. The SDK can then generate or reuse message ids during import.
Use messageId only when your plugin deliberately manages stable message ids and returns matching message ids in messages.
// By message ID
type VariantImport = {
id?: string;
messageId: string;
matches: Match[];
pattern: Pattern;
};
// By bundle/locale
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