Skip to main content
Glama
keys.formatter.ts29.6 kB
import type { BulkResult, CursorPaginatedResult, Key, KeyDeleted, KeysBulkDeleted, } from "@lokalise/node-api"; import { formatBulletList, formatEmptyState, formatErrorList, formatFooter, formatHeading, formatPaginationInfo, formatPercentage, formatSafeArray, formatTable, formatTruncated, } from "../../shared/utils/formatter.util.js"; // Helper to consistently render key_name (object per platform) as a single display string function displayKeyName(keyName: unknown): string { if (keyName == null) return "Unnamed"; if (typeof keyName === "string") return keyName; if (typeof keyName === "object") { const names = keyName as Record<string, string>; return names.web || names.ios || names.android || names.other || "Unnamed"; } return String(keyName); } /** * @namespace KeysFormatter * @description Utility functions for formatting Lokalise Keys API responses into readable formats. * Provides consistent Markdown formatting for various key operations. */ /** * @function formatKeysList * @description Formats a list of keys into a comprehensive, LLM-friendly Markdown report * @memberof KeysFormatter * @param {any} response - The raw response from the Lokalise Keys API list operation * @param {string} projectId - The project ID for context * @returns {string} Formatted Markdown string containing the keys list */ export function formatKeysList( response: CursorPaginatedResult<Key>, projectId: string, ): string { const keys = response.items || []; const hasNextCursor = response.hasNextCursor(); if (keys.length === 0) { const suggestions = [ "The project is newly created", "All keys have been deleted", "Filter criteria excluded all keys", "You may not have permission to view keys", ]; return [ formatHeading("Translation Keys Analysis", 1), "", formatHeading(`Project: ${projectId}`, 2), "", formatEmptyState("keys", "this project", suggestions), formatFooter("Analysis completed"), ].join("\n"); } const lines: string[] = []; lines.push(formatHeading("Translation Keys Analysis", 1)); lines.push(""); lines.push(formatHeading(`Project: ${projectId}`, 2)); lines.push(""); // Helper function to get primary key name (uses shared display helper) const getPrimaryKeyName = (keyName: unknown): string => displayKeyName(keyName); // Calculate comprehensive statistics const stats = { totalKeys: keys.length, platformCounts: { ios: keys.filter((k) => k.platforms?.includes("ios")).length, android: keys.filter((k) => k.platforms?.includes("android")).length, web: keys.filter((k) => k.platforms?.includes("web")).length, other: keys.filter((k) => k.platforms?.includes("other")).length, }, statusCounts: { hidden: keys.filter((k) => k.is_hidden).length, archived: keys.filter((k) => k.is_archived).length, plural: keys.filter((k) => k.is_plural).length, withDescription: keys.filter( (k) => k.description && k.description.trim() !== "", ).length, withTags: keys.filter((k) => k.tags && k.tags.length > 0).length, withComments: keys.filter((k) => k.comments && k.comments.length > 0) .length, withScreenshots: keys.filter( (k) => k.screenshots && k.screenshots.length > 0, ).length, }, translationCounts: { total: keys.reduce((sum, k) => sum + (k.translations?.length || 0), 0), withTranslations: keys.filter( (k) => k.translations && k.translations.length > 0, ).length, }, }; // Executive summary lines.push(formatHeading("Executive Summary", 2)); lines.push(""); lines.push(`**${stats.totalKeys} translation keys** found in this project.`); lines.push(""); // Key Distribution lines.push(formatHeading("Key Distribution", 3)); lines.push(""); for (const [platform, count] of Object.entries(stats.platformCounts)) { const percentage = formatPercentage(count, stats.totalKeys); lines.push( `- **${platform.toUpperCase()} Platform:** ${count} keys (${percentage})`, ); } lines.push(""); // Content Quality Indicators lines.push(formatHeading("Content Quality Indicators", 3)); lines.push(""); const qualityStats = [ { label: "Keys with Descriptions", value: stats.statusCounts.withDescription, }, { label: "Keys with Tags", value: stats.statusCounts.withTags }, { label: "Keys with Comments", value: stats.statusCounts.withComments }, { label: "Keys with Screenshots", value: stats.statusCounts.withScreenshots, }, ]; for (const stat of qualityStats) { const percentage = formatPercentage(stat.value, stats.totalKeys); lines.push( `- **${stat.label}:** ${stat.value}/${stats.totalKeys} (${percentage})`, ); } lines.push(""); // Status Overview lines.push(formatHeading("Status Overview", 3)); lines.push(""); const statusStats = [ { label: "Hidden Keys", value: stats.statusCounts.hidden }, { label: "Archived Keys", value: stats.statusCounts.archived }, { label: "Plural Keys", value: stats.statusCounts.plural }, ]; for (const stat of statusStats) { const percentage = formatPercentage(stat.value, stats.totalKeys); lines.push(`- **${stat.label}:** ${stat.value} (${percentage})`); } lines.push(""); if (stats.translationCounts.withTranslations > 0) { // Translation insights lines.push(formatHeading("Translation Status", 3)); lines.push(""); lines.push( `- **Keys with Translation Data:** ${stats.translationCounts.withTranslations}/${stats.totalKeys}`, ); lines.push(`- **Total Translations:** ${stats.translationCounts.total}`); const avgTranslations = Math.round( (stats.translationCounts.total / stats.translationCounts.withTranslations) * 100, ) / 100; lines.push(`- **Average Translations per Key:** ${avgTranslations}`); lines.push(""); } // Pagination information lines.push( formatPaginationInfo(hasNextCursor, response.nextCursor, keys.length), ); // Detailed keys table with enhanced information lines.push(formatHeading("Detailed Keys Inventory", 2)); lines.push(""); const tableData = keys.map((key) => ({ id: key.key_id || "N/A", keyName: getPrimaryKeyName(key.key_name), description: key.description || "*No description*", platforms: formatSafeArray(key.platforms), tags: key.tags && key.tags.length > 0 ? key.tags.length > 2 ? `${key.tags.slice(0, 2).join(", ")}+${key.tags.length - 2}` : key.tags.join(", ") : "None", status: getKeyStatus(key), context: key.context || "None", })); const table = formatTable(tableData, [ { key: "id", header: "ID" }, { key: "keyName", header: "Key Name", formatter: (v) => `\`${v}\`` }, { key: "description", header: "Description", maxWidth: 40 }, { key: "platforms", header: "Platforms" }, { key: "tags", header: "Tags" }, { key: "status", header: "Status" }, { key: "context", header: "Context", maxWidth: 30 }, ]); lines.push(table); lines.push(""); // Advanced insights section lines.push(formatHeading("Advanced Insights", 2)); lines.push(""); // Find keys that might need attention const recommendations: string[] = []; const missingDescription = keys.filter( (k) => !k.description || k.description.trim() === "", ); const noTags = keys.filter((k) => !k.tags || k.tags.length === 0); const noPlatforms = keys.filter( (k) => !k.platforms || k.platforms.length === 0, ); const hiddenKeys = keys.filter((k) => k.is_hidden); const archivedKeys = keys.filter((k) => k.is_archived); if (missingDescription.length > 0) { recommendations.push( `**${missingDescription.length} keys without descriptions** - consider adding descriptions for better translator context`, ); } if (noTags.length > 0) { recommendations.push( `**${noTags.length} keys without tags** - tags help with organization and filtering`, ); } if (noPlatforms.length > 0) { recommendations.push( `**${noPlatforms.length} keys without platform assignments** - may indicate configuration issues`, ); } if (hiddenKeys.length > 0) { recommendations.push( `**${hiddenKeys.length} hidden keys** - verify these should remain hidden from translators`, ); } if (archivedKeys.length > 0) { recommendations.push( `**${archivedKeys.length} archived keys** - these are inactive and may need cleanup`, ); } if (recommendations.length > 0) { lines.push(formatHeading("Recommendations for Improvement", 3)); lines.push(""); for (const recommendation of recommendations) { lines.push(`- ${recommendation}`); } lines.push(""); } // Platform-specific insights lines.push(formatHeading("Platform-Specific Analysis", 3)); lines.push(""); const platformData = [ { name: "iOS", count: stats.platformCounts.ios }, { name: "Android", count: stats.platformCounts.android }, { name: "Web", count: stats.platformCounts.web }, { name: "Other", count: stats.platformCounts.other }, ]; for (const platform of platformData) { const percentage = Math.round((platform.count / stats.totalKeys) * 100); let coverage: string; if (percentage === 100) { coverage = "- Universal coverage ✅"; } else if (percentage > 75) { coverage = "- High coverage"; } else if (percentage > 50) { coverage = "- Moderate coverage"; } else if (percentage > 0) { coverage = "- Limited coverage ⚠️"; } else { coverage = "- No keys assigned ❌"; } lines.push( `- **${platform.name}:** ${platform.count} keys (${percentage}%) ${coverage}`, ); } lines.push(""); // Summary section for LLM reasoning lines.push(formatHeading("Summary for Analysis", 2)); lines.push(""); lines.push( `**Project ${projectId} contains ${stats.totalKeys} translation keys** with the following characteristics:`, ); lines.push(""); const mostUsedPlatform = Object.entries(stats.platformCounts).reduce( (a, b) => (a[1] > b[1] ? a : b), )[0]; const summaryStats = [ `Most used platform: ${mostUsedPlatform}`, `Content maturity: ${formatPercentage(stats.statusCounts.withDescription, stats.totalKeys)} of keys have descriptions`, `Organization level: ${formatPercentage(stats.statusCounts.withTags, stats.totalKeys)} of keys are tagged`, `Collaboration activity: ${stats.statusCounts.withComments} keys have comments`, `Visual context: ${stats.statusCounts.withScreenshots} keys have screenshots`, ]; for (const stat of summaryStats) { lines.push(`- **${stat}**`); } if (stats.statusCounts.archived > 0 || stats.statusCounts.hidden > 0) { lines.push( `- **Maintenance needed:** ${stats.statusCounts.archived + stats.statusCounts.hidden} keys are archived or hidden`, ); } lines.push(""); // Final metadata const contextMessage = hasNextCursor ? "Additional keys available - use cursor pagination to fetch more" : `Showing ${keys.length} keys from project \`${projectId}\``; lines.push(formatFooter("Analysis completed", contextMessage)); return lines.join("\n"); } /** * @function formatKeyDetails * @description Formats detailed information about a single key with comprehensive LLM-friendly output * @memberof KeysFormatter * @param {Key} key - The key object from Lokalise API * @param {string} projectId - The project ID for context * @returns {string} Formatted Markdown string containing the key details */ export function formatKeyDetails(key: Key, projectId: string): string { const lines: string[] = []; lines.push(formatHeading("Translation Key Details", 1)); lines.push(""); // Helper function to safely format objects const safeStringify = (obj: unknown): string => { if (obj === null || obj === undefined) return "Not set"; if (typeof obj === "string") return obj; if (typeof obj === "number" || typeof obj === "boolean") return String(obj); try { return JSON.stringify(obj, null, 2); } catch { return String(obj); } }; // Helper function to format platform-specific names/filenames const formatPlatformObject = (obj: unknown): string => { if (!obj || typeof obj !== "object") return "Not configured"; const platforms = ["ios", "android", "web", "other"] as const; const items: string[] = []; for (const platform of platforms) { const value = (obj as Record<string, string>)[platform]; if (value) { items.push(`- **${platform.toUpperCase()}:** \`${value}\``); } } return items.length > 0 ? items.join("\n") : "No platform-specific values set"; }; // Core identification const coreInfo = { "Key ID": key.key_id, "Project ID": `\`${projectId}\``, Description: key.description || "*No description provided*", Context: key.context || "*No context specified*", }; lines.push(formatHeading("Core Identification", 2)); lines.push(formatBulletList(coreInfo)); lines.push(""); // Platform-specific key names lines.push(formatHeading("Platform-Specific Key Names", 2)); lines.push(""); lines.push(formatPlatformObject(key.key_name)); lines.push(""); // Platform-specific filenames lines.push(formatHeading("Platform-Specific Filenames", 2)); lines.push(""); lines.push(formatPlatformObject(key.filenames)); lines.push(""); // Platform assignments lines.push(formatHeading("Platform Assignments", 2)); lines.push(""); if (key.platforms && key.platforms.length > 0) { lines.push(`**Assigned Platforms:** ${key.platforms.length}`); lines.push(""); for (const platform of key.platforms) { lines.push(`- **${platform.toUpperCase()}** - Active on this platform`); } } else { lines.push( "*No platforms assigned - this key is not targeted to any specific platform*", ); } lines.push(""); // Tags and organization lines.push(formatHeading("Tags and Organization", 2)); lines.push(""); if (key.tags && key.tags.length > 0) { lines.push(`**Total Tags:** ${key.tags.length}`); lines.push(""); for (const tag of key.tags) { lines.push(`- \`${tag}\``); } } else { lines.push( "*No tags assigned - consider adding tags for better organization*", ); } lines.push(""); // Content specifications const contentInfo = { "Character Limit": key.char_limit > 0 ? `${key.char_limit} characters` : "No limit set", "Base Word Count": key.base_words > 0 ? `${key.base_words} words` : "Not calculated", "Supports Pluralization": key.is_plural ? "Yes" : "No", "Plural Form Name": key.is_plural ? `\`${key.plural_name}\`` : "Not set", }; lines.push(formatHeading("Content Specifications", 2)); lines.push(formatBulletList(contentInfo)); lines.push(""); // Status and visibility const statusInfo = { Visibility: key.is_hidden ? "🔒 Hidden from translators" : "👁️ Visible to translators", "Archive Status": key.is_archived ? "📦 Archived (inactive)" : "✅ Active", }; lines.push(formatHeading("Status and Visibility", 2)); lines.push(formatBulletList(statusInfo)); lines.push(""); // Timeline information const timelineInfo = { Created: `${key.created_at} (${new Date(key.created_at).toLocaleDateString()})`, "Last Modified": `${key.modified_at} (${new Date(key.modified_at).toLocaleDateString()})`, "Translations Last Modified": `${key.translations_modified_at} (${new Date(key.translations_modified_at).toLocaleDateString()})`, }; lines.push(formatHeading("Timeline Information", 2)); lines.push(formatBulletList(timelineInfo)); lines.push(""); if (key.custom_attributes) { // Custom attributes lines.push(formatHeading("Custom Attributes", 2)); lines.push(""); try { const parsed = JSON.parse(key.custom_attributes); if (Object.keys(parsed).length > 0) { for (const [attrKey, attrValue] of Object.entries(parsed)) { lines.push(`- **${attrKey}:** ${safeStringify(attrValue)}`); } } else { lines.push("*No custom attributes defined*"); } } catch { lines.push(`Raw value: \`${key.custom_attributes}\``); } lines.push(""); } if (key.translations && key.translations.length > 0) { // Translation data (if included) lines.push(formatHeading("Translation Status", 2)); lines.push(""); lines.push(`**Total Languages:** ${key.translations.length}`); lines.push(""); // Translation statistics const reviewedCount = key.translations.filter((t) => t.is_reviewed).length; const unverifiedCount = key.translations.filter( (t) => t.is_unverified, ).length; const emptyCount = key.translations.filter( (t) => !t.translation || t.translation.trim() === "", ).length; const translationStats = { Reviewed: `${reviewedCount}/${key.translations.length} (${formatPercentage(reviewedCount, key.translations.length)})`, Unverified: `${unverifiedCount}/${key.translations.length} (${formatPercentage(unverifiedCount, key.translations.length)})`, "Empty/Missing": `${emptyCount}/${key.translations.length} (${formatPercentage(emptyCount, key.translations.length)})`, }; lines.push(formatHeading("Translation Statistics", 3)); lines.push(formatBulletList(translationStats)); lines.push(""); // Translation details table lines.push(formatHeading("Translation Details", 3)); lines.push(""); const translationData = key.translations.map((translation) => ({ language: translation.language_iso || "Unknown", translation: translation.translation || "*Empty*", status: getTranslationStatus(translation), wordCount: translation.words || 0, lastModified: translation.modified_at ? new Date(translation.modified_at).toLocaleDateString() : "Not set", })); const translationTable = formatTable(translationData, [ { key: "language", header: "Language" }, { key: "translation", header: "Translation", maxWidth: 60 }, { key: "status", header: "Status" }, { key: "wordCount", header: "Word Count" }, { key: "lastModified", header: "Last Modified" }, ]); lines.push(translationTable); lines.push(""); } else { lines.push(formatHeading("Translation Status", 2)); lines.push( "*No translation data included in this response. Use includeTranslations parameter to fetch translation details.*", ); lines.push(""); } // Comments summary lines.push(formatHeading("Comments and Collaboration", 2)); lines.push(""); if (key.comments && key.comments.length > 0) { lines.push(`**Total Comments:** ${key.comments.length}`); lines.push(""); for (let i = 0; i < Math.min(key.comments.length, 5); i++) { const comment = key.comments[i]; const commentDate = comment.added_at ? new Date(comment.added_at).toLocaleDateString() : "Unknown date"; const commentText = comment.comment ? formatTruncated(comment.comment, 100) : "*Empty comment*"; lines.push(`**Comment ${i + 1}** (${commentDate}): ${commentText}`); lines.push(""); } if (key.comments.length > 5) { lines.push(`*... and ${key.comments.length - 5} more comments*`); lines.push(""); } } else { lines.push("*No comments on this key yet*"); lines.push(""); } // Screenshots summary lines.push(formatHeading("Visual Context", 2)); lines.push(""); if (key.screenshots && key.screenshots.length > 0) { lines.push(`**Total Screenshots:** ${key.screenshots.length}`); lines.push(""); for (let i = 0; i < Math.min(key.screenshots.length, 3); i++) { const screenshot = key.screenshots[i]; const title = screenshot.title || `Screenshot ${i + 1}`; const description = screenshot.description || "No description"; lines.push(`**${title}:** ${description}`); lines.push(""); } if (key.screenshots.length > 3) { lines.push(`*... and ${key.screenshots.length - 3} more screenshots*`); lines.push(""); } } else { lines.push( "*No screenshots attached - consider adding visual context for better translations*", ); lines.push(""); } // Summary for LLM reasoning lines.push(formatHeading("Summary for Analysis", 2)); lines.push("**Key Characteristics:**"); const summaryStats = [ `${key.platforms?.length || 0} platform(s) targeted`, `${key.translations?.length || 0} language(s) configured`, `${key.tags?.length || 0} organizational tag(s)`, `${key.comments?.length || 0} collaboration comment(s)`, `${key.screenshots?.length || 0} visual context item(s)`, `Content status: ${key.is_archived ? "Archived" : "Active"} ${key.is_hidden ? "(Hidden)" : "(Visible)"}`, ]; for (const stat of summaryStats) { lines.push(`- ${stat}`); } if (key.translations && key.translations.length > 0) { const completionRate = Math.round( (key.translations.filter( (t) => t.translation && t.translation.trim() !== "", ).length / key.translations.length) * 100, ); lines.push(`- Translation completion: ${completionRate}%`); } lines.push(""); lines.push(formatFooter("Key details retrieved")); return lines.join("\n"); } /** * @function formatCreateKeysResult * @description Formats the result of a bulk key creation operation * @memberof KeysFormatter * @param {any} response - The raw response from the Lokalise Keys API create operation * @param {string} projectId - The project ID for context * @returns {string} Formatted Markdown string containing the creation results */ export function formatCreateKeysResult( response: BulkResult<Key>, projectId: string, ): string { const createdKeys = response.items || []; const errors = response.errors || []; const lines: string[] = []; lines.push(formatHeading("Keys Creation Results", 1)); lines.push(""); lines.push(`**Project ID:** \`${projectId}\``); lines.push(""); // Summary const summaryInfo = { "Successfully Created": `${createdKeys.length} keys`, Errors: errors.length, "Total Requested": createdKeys.length + errors.length, }; lines.push(formatHeading("Summary", 2)); lines.push(formatBulletList(summaryInfo)); lines.push(""); // Successfully created keys if (createdKeys.length > 0) { lines.push(formatHeading("✅ Successfully Created Keys", 2)); lines.push(""); const tableData = createdKeys.map((key) => ({ keyId: key.key_id || "N/A", keyName: displayKeyName(key.key_name), platforms: formatSafeArray(key.platforms), })); const table = formatTable(tableData, [ { key: "keyId", header: "Key ID" }, { key: "keyName", header: "Key Name", formatter: (v) => `\`${v}\`` }, { key: "platforms", header: "Platforms" }, ]); lines.push(table); lines.push(""); } // Errors lines.push( formatErrorList( errors.map((e) => ({ ...e, code: e.code !== undefined ? String(e.code) : undefined, })), ), ); lines.push(formatFooter("Keys creation completed")); return lines.join("\n"); } /** * @function formatUpdateKeyResult * @description Formats the result of a key update operation * @memberof KeysFormatter * @param {Key} key - The updated key object from Lokalise API * @param {string} projectId - The project ID for context * @returns {string} Formatted Markdown string containing the update results */ export function formatUpdateKeyResult(key: Key, projectId: string): string { const lines: string[] = []; lines.push(formatHeading("Key Update Successful", 1)); lines.push(""); lines.push( `**Key \`${key.key_name}\` (ID: ${key.key_id}) has been updated successfully.**`, ); lines.push(""); // Updated details const keyInfo = { "Key ID": key.key_id, "Key Name": `\`${key.key_name}\``, "Project ID": `\`${projectId}\``, Description: key.description || "No description", "Last Modified": key.modified_at, }; lines.push(formatHeading("Updated Key Details", 2)); lines.push(formatBulletList(keyInfo)); lines.push(""); // Platforms lines.push(formatHeading("Platforms", 3)); if (key.platforms && key.platforms.length > 0) { for (const platform of key.platforms) { lines.push(`- ${platform}`); } } else { lines.push("*No platforms specified*"); } lines.push(""); // Tags lines.push(formatHeading("Tags", 3)); if (key.tags && key.tags.length > 0) { for (const tag of key.tags) { lines.push(`- \`${tag}\``); } } else { lines.push("*No tags assigned*"); } lines.push(""); lines.push(formatFooter("Key updated")); return lines.join("\n"); } /** * @function formatDeleteKeyResult * @description Formats the result of a key deletion operation * @memberof KeysFormatter * @param {any} response - The raw response from the Lokalise Keys API delete operation * @param {string} projectId - The project ID for context * @param {number} keyId - The deleted key ID * @returns {string} Formatted Markdown string containing the deletion confirmation */ export function formatDeleteKeyResult( response: KeyDeleted, projectId: string, keyId: number, ): string { const lines: string[] = []; lines.push(formatHeading("Key Deletion Successful", 1)); lines.push(""); lines.push( `**Key with ID ${keyId} has been successfully deleted from project \`${projectId}\`.**`, ); lines.push(""); const deletionInfo = { "Deleted Key ID": keyId, "Project ID": `\`${projectId}\``, "Deletion Time": new Date().toISOString(), Status: response.key_removed ? "✅ Confirmed removed" : "Processing", }; lines.push(formatHeading("Details", 2)); lines.push(formatBulletList(deletionInfo)); lines.push(""); lines.push( "> **Note:** This action cannot be undone. All translations associated with this key have also been deleted.", ); lines.push(""); lines.push(formatFooter("Key deleted")); return lines.join("\n"); } /** * @function formatBulkUpdateKeysResult * @description Formats the result of a bulk key update operation * @memberof KeysFormatter * @param {any} response - The raw response from the Lokalise Keys API bulk update operation * @param {string} projectId - The project ID for context * @returns {string} Formatted Markdown string containing the bulk update results */ export function formatBulkUpdateKeysResult( response: BulkResult<Key>, projectId: string, ): string { const updatedKeys = response.items || []; const errors = response.errors || []; const lines: string[] = []; lines.push(formatHeading("Bulk Keys Update Results", 1)); lines.push(""); lines.push(`**Project ID:** \`${projectId}\``); lines.push(""); // Summary const summaryInfo = { "Successfully Updated": `${updatedKeys.length} keys`, Errors: errors.length, "Total Requested": updatedKeys.length + errors.length, }; lines.push(formatHeading("Summary", 2)); lines.push(formatBulletList(summaryInfo)); lines.push(""); // Successfully updated keys if (updatedKeys.length > 0) { lines.push(formatHeading("✅ Successfully Updated Keys", 2)); lines.push(""); const tableData = updatedKeys.map((key) => ({ keyId: key.key_id || "N/A", keyName: displayKeyName(key.key_name), lastModified: key.modified_at || "Unknown", })); const table = formatTable(tableData, [ { key: "keyId", header: "Key ID" }, { key: "keyName", header: "Key Name", formatter: (v) => `\`${v}\`` }, { key: "lastModified", header: "Last Modified" }, ]); lines.push(table); lines.push(""); } // Errors lines.push( formatErrorList( errors.map((e) => ({ ...e, code: e.code !== undefined ? String(e.code) : undefined, })), ), ); lines.push(formatFooter("Bulk update completed")); return lines.join("\n"); } /** * @function formatBulkDeleteKeysResult * @description Formats the result of a bulk key deletion operation * @memberof KeysFormatter * @param {any} response - The raw response from the Lokalise Keys API bulk delete operation * @param {string} projectId - The project ID for context * @param {number} requestedCount - The number of keys requested to be deleted * @returns {string} Formatted Markdown string containing the bulk deletion results */ export function formatBulkDeleteKeysResult( response: KeysBulkDeleted, projectId: string, requestedCount: number, ): string { const lines: string[] = []; lines.push(formatHeading("Bulk Keys Deletion Successful", 1)); lines.push(""); lines.push( `**${requestedCount} keys have been successfully deleted from project \`${projectId}\`.**`, ); lines.push(""); const deletionInfo = { "Deleted Keys Count": requestedCount, "Project ID": `\`${projectId}\``, "Deletion Time": new Date().toISOString(), Status: response.keys_removed ? "✅ All keys confirmed removed" : "Processing", }; lines.push(formatHeading("Details", 2)); lines.push(formatBulletList(deletionInfo)); lines.push(""); lines.push( "> **Note:** This action cannot be undone. All translations associated with these keys have also been deleted.", ); lines.push(""); lines.push(formatFooter("Bulk deletion completed")); return lines.join("\n"); } // Helper functions function getKeyStatus(key: Key): string { const statusParts: string[] = []; if (key.is_hidden) statusParts.push("🔒Hidden"); if (key.is_archived) statusParts.push("📦Archived"); if (key.is_plural) statusParts.push("🔢Plural"); if (key.comments && key.comments.length > 0) statusParts.push(`💬${key.comments.length}`); if (key.screenshots && key.screenshots.length > 0) statusParts.push(`📷${key.screenshots.length}`); return statusParts.length > 0 ? statusParts.join(" ") : "✅Standard"; } function getTranslationStatus(translation: unknown): string { if (!translation || typeof translation !== "object") { return "❌ Missing"; } if ( "translation" in translation && typeof translation.translation === "string" && translation.translation.trim() !== "" ) { return "✅ Reviewed"; } if ("is_reviewed" in translation && translation.is_reviewed) { return "✅ Reviewed"; } if ("translation" in translation && translation.translation === "") { return "❌ Empty"; } if ("is_unverified" in translation && translation.is_unverified) { return "⚠️ Unverified"; } return "⏳ Pending Review"; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AbdallahAHO/lokalise-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server