lokalise-mcp
Version:
The Lokalise MCP Server brings Lokalise's localization power to Claude and AI assistants—manage projects, keys, and translations by chat.
417 lines (416 loc) • 22 kB
JavaScript
import { config } from "../../shared/utils/config.util.js";
import { ErrorType, McpError } from "../../shared/utils/error.util.js";
import { buildErrorContext, handleControllerError, } from "../../shared/utils/error-handler.util.js";
import { Logger } from "../../shared/utils/logger.util.js";
import { formatAddLanguagesResult, formatLanguageDetails, formatProjectLanguages, formatRemoveLanguageResult, formatSystemLanguagesList, formatUpdateLanguageResult, } from "./languages.formatter.js";
import * as languagesService from "./languages.service.js";
/**
* @namespace LanguagesController
* @description Controller responsible for handling Lokalise Languages API operations.
* It orchestrates calls to the languages service, applies defaults,
* maps options, and formats the response using the formatter.
*/
/**
* @function listSystemLanguages
* @description Fetches a list of system languages from Lokalise.
* @memberof LanguagesController
* @param {ListSystemLanguagesToolArgsType} args - Arguments containing pagination options
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted system languages list in Markdown.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function listSystemLanguages(args = {}) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "listSystemLanguages");
methodLogger.debug("Getting Lokalise system languages list...");
try {
// Detect if we're running in a test environment
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Apply defaults and validation
const options = {
limit: args.limit,
page: args.page,
};
if (options.limit !== undefined &&
(options.limit < 1 || options.limit > 500)) {
// Validate limit if provided
throw new McpError("Invalid limit parameter. Must be between 1 and 500.", ErrorType.API_ERROR);
}
if (options.page !== undefined && options.page < 1) {
// Validate page if provided
throw new McpError("Invalid page parameter. Must be 1 or greater.", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Getting system languages from Lokalise", {
originalOptions: args,
options,
isTestEnvironment,
hasApiToken,
});
try {
// Call the service with the options
const languages = await languagesService.default.getSystemLanguages(options);
methodLogger.debug("Got the response from the service", {
languageCount: languages.length,
});
const formattedContent = formatSystemLanguagesList(languages);
return { content: formattedContent };
}
catch (error) {
// Handle specific Lokalise API errors
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
// For other errors, rethrow
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise System Languages", "listSystemLanguages", "controllers/languages.controller.ts@listSystemLanguages", `limit: ${args.limit}, page: ${args.page}`, { args }));
}
}
/**
* @function listProjectLanguages
* @description Lists all languages in a specific Lokalise project.
* @memberof LanguagesController
* @param {ListProjectLanguagesToolArgsType} args - Arguments containing project and language options
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted languages list in Markdown.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function listProjectLanguages(args) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "listProjectLanguages");
methodLogger.debug("Getting Lokalise project languages...", args);
try {
// Detect if we're running in a test environment
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Validate project ID
if (!args.projectId || typeof args.projectId !== "string") {
throw new McpError("Project ID is required and must be a string.", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Getting project languages from Lokalise", {
projectId: args.projectId,
isTestEnvironment,
hasApiToken,
});
try {
// Call the service with the project ID
const languages = await languagesService.default.getProjectLanguages(args.projectId);
methodLogger.debug("Got the project languages from the service", {
projectId: args.projectId,
languageCount: languages.length,
});
const formattedContent = formatProjectLanguages(languages, args.projectId, args.includeProgress);
return { content: formattedContent };
}
catch (error) {
// Handle specific Lokalise API errors
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
if (error instanceof McpError &&
(error.message.includes("Not Found") || error.message.includes("404"))) {
methodLogger.error("Project not found", { projectId: args.projectId });
throw new McpError(`Project with ID '${args.projectId}' not found. Please check the project ID.`, ErrorType.API_ERROR);
}
// For other errors, rethrow
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise Project Languages", "listProjectLanguages", "controllers/languages.controller.ts@listProjectLanguages", `projectId: ${args.projectId}`, { args }));
}
}
/**
* @function addProjectLanguages
* @description Adds languages to a Lokalise project.
* @memberof LanguagesController
* @param {AddProjectLanguagesToolArgsType} args - Arguments containing project and languages data
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted addition result.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function addProjectLanguages(args) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "addProjectLanguages");
methodLogger.debug("Adding languages to Lokalise project...", {
projectId: args.projectId,
languageCount: args.languages?.length,
});
try {
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Validate project ID
if (!args.projectId || typeof args.projectId !== "string") {
throw new McpError("Project ID is required and must be a string.", ErrorType.API_ERROR);
}
// Validate languages array
if (!args.languages ||
!Array.isArray(args.languages) ||
args.languages.length === 0) {
throw new McpError("Languages array is required and must contain at least one language.", ErrorType.API_ERROR);
}
if (args.languages.length > 100) {
throw new McpError("Cannot add more than 100 languages at once.", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Adding languages to Lokalise", {
projectId: args.projectId,
languageCount: args.languages.length,
isTestEnvironment,
hasApiToken,
});
try {
const result = await languagesService.default.addProjectLanguages(args.projectId, args.languages);
methodLogger.debug("Languages added successfully", {
addedCount: result.length,
});
const formattedContent = formatAddLanguagesResult(result, args.projectId);
return { content: formattedContent };
}
catch (error) {
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
if (error instanceof McpError &&
(error.message.includes("Not Found") || error.message.includes("404"))) {
methodLogger.error("Project not found", { projectId: args.projectId });
throw new McpError(`Project with ID '${args.projectId}' not found. Please check the project ID.`, ErrorType.API_ERROR);
}
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise Languages", "addProjectLanguages", "controllers/languages.controller.ts@addProjectLanguages", `projectId: ${args.projectId}, languageCount: ${args.languages?.length}`, {
args: { ...args, languages: `[${args.languages?.length} languages]` },
}));
}
}
/**
* @function getLanguage
* @description Gets detailed information about a specific language.
* @memberof LanguagesController
* @param {GetLanguageToolArgsType} args - Arguments containing language details options
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted language details.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function getLanguage(args) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "getLanguage");
methodLogger.debug("Getting Lokalise language details...", args);
try {
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Validate project ID
if (!args.projectId || typeof args.projectId !== "string") {
throw new McpError("Project ID is required and must be a string.", ErrorType.API_ERROR);
}
// Validate language ID
if (!args.languageId ||
typeof args.languageId !== "number" ||
args.languageId <= 0) {
throw new McpError("Language ID is required and must be a positive number.", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Getting language details from Lokalise", {
projectId: args.projectId,
languageId: args.languageId,
isTestEnvironment,
hasApiToken,
});
try {
const language = await languagesService.default.getLanguage(args.projectId, args.languageId);
methodLogger.debug("Got language details from service", {
languageId: language.lang_id,
languageName: language.lang_name,
});
const formattedContent = formatLanguageDetails(language, args.projectId);
return { content: formattedContent };
}
catch (error) {
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
if (error instanceof McpError &&
(error.message.includes("Not Found") || error.message.includes("404"))) {
methodLogger.error("Language or project not found", args);
throw new McpError(`Language with ID '${args.languageId}' not found in project '${args.projectId}'. Please check the IDs.`, ErrorType.API_ERROR);
}
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise Language", "getLanguage", "controllers/languages.controller.ts@getLanguage", `projectId: ${args.projectId}, languageId: ${args.languageId}`, { args }));
}
}
/**
* @function updateLanguage
* @description Updates an existing language.
* @memberof LanguagesController
* @param {UpdateLanguageToolArgsType} args - Arguments containing language update options
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted update result.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function updateLanguage(args) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "updateLanguage");
methodLogger.debug("Updating Lokalise language...", {
projectId: args.projectId,
languageId: args.languageId,
});
try {
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Validate project ID
if (!args.projectId || typeof args.projectId !== "string") {
throw new McpError("Project ID is required and must be a string.", ErrorType.API_ERROR);
}
// Validate language ID
if (!args.languageId ||
typeof args.languageId !== "number" ||
args.languageId <= 0) {
throw new McpError("Language ID is required and must be a positive number.", ErrorType.API_ERROR);
}
// Validate language data
if (!args.languageData || typeof args.languageData !== "object") {
throw new McpError("Language data is required and must be an object.", ErrorType.API_ERROR);
}
// Check that at least one field is provided for update
if (Object.keys(args.languageData).length === 0) {
throw new McpError("At least one field must be provided to update (lang_iso, lang_name, or plural_forms).", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Updating language in Lokalise", {
projectId: args.projectId,
languageId: args.languageId,
updateFields: Object.keys(args.languageData),
isTestEnvironment,
hasApiToken,
});
try {
const language = await languagesService.default.updateLanguage(args.projectId, args.languageId, args.languageData);
methodLogger.debug("Language updated successfully", {
languageId: language.lang_id,
languageName: language.lang_name,
});
const formattedContent = formatUpdateLanguageResult(language, args.projectId);
return { content: formattedContent };
}
catch (error) {
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
if (error instanceof McpError &&
(error.message.includes("Not Found") || error.message.includes("404"))) {
methodLogger.error("Language or project not found", args);
throw new McpError(`Language with ID '${args.languageId}' not found in project '${args.projectId}'. Please check the IDs.`, ErrorType.API_ERROR);
}
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise Language", "updateLanguage", "controllers/languages.controller.ts@updateLanguage", `projectId: ${args.projectId}, languageId: ${args.languageId}`, { args: { ...args, languageData: "[languageData object]" } }));
}
}
/**
* @function removeLanguage
* @description Removes a language from a Lokalise project.
* @memberof LanguagesController
* @param {RemoveLanguageToolArgsType} args - Arguments containing language removal options
* @returns {Promise<ControllerResponse>} A promise that resolves to the standard controller response containing the formatted removal result.
* @throws {McpError} Throws an McpError (handled by `handleControllerError`) if the service call fails or returns an error.
*/
async function removeLanguage(args) {
const methodLogger = Logger.forContext("controllers/languages.controller.ts", "removeLanguage");
methodLogger.debug("Removing Lokalise language...", args);
try {
const isTestEnvironment = process.env.NODE_ENV === "test" ||
process.env.JEST_WORKER_ID !== undefined;
// Validate project ID
if (!args.projectId || typeof args.projectId !== "string") {
throw new McpError("Project ID is required and must be a string.", ErrorType.API_ERROR);
}
// Validate language ID
if (!args.languageId ||
typeof args.languageId !== "number" ||
args.languageId <= 0) {
throw new McpError("Language ID is required and must be a positive number.", ErrorType.API_ERROR);
}
// Check for API token
const hasApiToken = Boolean(config.get("LOKALISE_API_KEY"));
if (!hasApiToken && !isTestEnvironment) {
throw new McpError("Lokalise API token is required. Please set LOKALISE_API_KEY environment variable.", ErrorType.AUTH_MISSING);
}
methodLogger.debug("Removing language from Lokalise", {
projectId: args.projectId,
languageId: args.languageId,
isTestEnvironment,
hasApiToken,
});
try {
await languagesService.default.removeLanguage(args.projectId, args.languageId);
methodLogger.debug("Language removed successfully", {
projectId: args.projectId,
languageId: args.languageId,
});
const formattedContent = formatRemoveLanguageResult(args.languageId, args.projectId);
return { content: formattedContent };
}
catch (error) {
if (error instanceof McpError &&
(error.message.includes("Unauthorized") ||
error.message.includes("Invalid API token"))) {
methodLogger.error("Lokalise API authentication failed");
throw new McpError("Lokalise API authentication failed. Please check your API token.", ErrorType.AUTH_INVALID);
}
if (error instanceof McpError &&
(error.message.includes("Not Found") || error.message.includes("404"))) {
methodLogger.error("Language or project not found", args);
throw new McpError(`Language with ID '${args.languageId}' not found in project '${args.projectId}'. Please check the IDs.`, ErrorType.API_ERROR);
}
throw error;
}
}
catch (error) {
throw handleControllerError(error, buildErrorContext("Lokalise Language", "removeLanguage", "controllers/languages.controller.ts@removeLanguage", `projectId: ${args.projectId}, languageId: ${args.languageId}`, { args }));
}
}
export default {
listSystemLanguages,
listProjectLanguages,
addProjectLanguages,
getLanguage,
updateLanguage,
removeLanguage,
};