string-catalog-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +8 -0
- package/README.md +127 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/mcp/prompts/batch-translate.d.ts +2 -0
- package/dist/mcp/prompts/batch-translate.js +88 -0
- package/dist/mcp/prompts/index.d.ts +2 -0
- package/dist/mcp/prompts/index.js +12 -0
- package/dist/mcp/prompts/review-translations.d.ts +2 -0
- package/dist/mcp/prompts/review-translations.js +75 -0
- package/dist/mcp/prompts/translate-strings.d.ts +2 -0
- package/dist/mcp/prompts/translate-strings.js +81 -0
- package/dist/mcp/tools/get-catalog-statistics.d.ts +2 -0
- package/dist/mcp/tools/get-catalog-statistics.js +25 -0
- package/dist/mcp/tools/get-translations-for-key.d.ts +2 -0
- package/dist/mcp/tools/get-translations-for-key.js +36 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +18 -0
- package/dist/mcp/tools/list-all-keys.d.ts +2 -0
- package/dist/mcp/tools/list-all-keys.js +44 -0
- package/dist/mcp/tools/list-supported-languages.d.ts +2 -0
- package/dist/mcp/tools/list-supported-languages.js +30 -0
- package/dist/mcp/tools/search-keys.d.ts +2 -0
- package/dist/mcp/tools/search-keys.js +32 -0
- package/dist/mcp/tools/update-translations.d.ts +2 -0
- package/dist/mcp/tools/update-translations.js +78 -0
- package/dist/string-catalog.d.ts +53 -0
- package/dist/string-catalog.js +220 -0
- package/dist/tools/get-catalog-statistics.d.ts +2 -0
- package/dist/tools/get-catalog-statistics.js +25 -0
- package/dist/tools/get-translations-for-key.d.ts +2 -0
- package/dist/tools/get-translations-for-key.js +36 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/list-all-keys.d.ts +2 -0
- package/dist/tools/list-all-keys.js +44 -0
- package/dist/tools/list-supported-languages.d.ts +2 -0
- package/dist/tools/list-supported-languages.js +30 -0
- package/dist/tools/search-keys.d.ts +2 -0
- package/dist/tools/search-keys.js +32 -0
- package/dist/tools/update-translations.d.ts +2 -0
- package/dist/tools/update-translations.js +78 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +6 -0
- package/eslint.config.js +23 -0
- package/images/mcp.jpeg +0 -0
- package/package.json +49 -0
- package/src/index.ts +25 -0
- package/src/mcp/prompts/batch-translate.ts +91 -0
- package/src/mcp/prompts/index.ts +10 -0
- package/src/mcp/prompts/review-translations.ts +79 -0
- package/src/mcp/prompts/translate-strings.ts +85 -0
- package/src/mcp/tools/get-catalog-statistics.ts +29 -0
- package/src/mcp/tools/get-translations-for-key.ts +45 -0
- package/src/mcp/tools/index.ts +16 -0
- package/src/mcp/tools/list-all-keys.ts +52 -0
- package/src/mcp/tools/list-supported-languages.ts +38 -0
- package/src/mcp/tools/search-keys.ts +40 -0
- package/src/mcp/tools/update-translations.ts +89 -0
- package/src/string-catalog.ts +232 -0
- package/src/types.ts +82 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerListSupportedLanguages = registerListSupportedLanguages;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const string_catalog_1 = require("../string-catalog");
|
|
6
|
+
function registerListSupportedLanguages(server) {
|
|
7
|
+
server.registerTool('list_supported_languages', {
|
|
8
|
+
description: 'List all supported languages in a given Xcode String Catalog (.xcstrings) file. Returns the source language and all languages that have translations.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
filePath: zod_1.z.string().describe('Absolute path to the .xcstrings file'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ filePath }) => {
|
|
13
|
+
const catalog = new string_catalog_1.StringCatalog(filePath);
|
|
14
|
+
const languages = catalog.getSupportedLanguages();
|
|
15
|
+
const sourceLanguage = catalog.getSourceLanguage();
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: 'text',
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
sourceLanguage,
|
|
22
|
+
supportedLanguages: languages,
|
|
23
|
+
count: languages.length,
|
|
24
|
+
}, null, 2),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlzdC1zdXBwb3J0ZWQtbGFuZ3VhZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rvb2xzL2xpc3Qtc3VwcG9ydGVkLWxhbmd1YWdlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUlBLHdFQWlDQztBQXBDRCw2QkFBd0I7QUFDeEIsc0RBQWtEO0FBRWxELFNBQWdCLDhCQUE4QixDQUFDLE1BQWlCO0lBQzVELE1BQU0sQ0FBQyxZQUFZLENBQ2YsMEJBQTBCLEVBQzFCO1FBQ0ksV0FBVyxFQUNQLHVKQUF1SjtRQUMzSixXQUFXLEVBQUU7WUFDVCxRQUFRLEVBQUUsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsQ0FBQyxzQ0FBc0MsQ0FBQztTQUN4RTtLQUNKLEVBQ0QsS0FBSyxFQUFFLEVBQUUsUUFBUSxFQUFFLEVBQUUsRUFBRTtRQUNuQixNQUFNLE9BQU8sR0FBRyxJQUFJLDhCQUFhLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUMsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDbEQsTUFBTSxjQUFjLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFFbkQsT0FBTztZQUNILE9BQU8sRUFBRTtnQkFDTDtvQkFDSSxJQUFJLEVBQUUsTUFBZTtvQkFDckIsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQ2hCO3dCQUNJLGNBQWM7d0JBQ2Qsa0JBQWtCLEVBQUUsU0FBUzt3QkFDN0IsS0FBSyxFQUFFLFNBQVMsQ0FBQyxNQUFNO3FCQUMxQixFQUNELElBQUksRUFDSixDQUFDLENBQ0o7aUJBQ0o7YUFDSjtTQUNKLENBQUM7SUFDTixDQUFDLENBQ0osQ0FBQztBQUNOLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBNY3BTZXJ2ZXIgfSBmcm9tICdAbW9kZWxjb250ZXh0cHJvdG9jb2wvc2RrL3NlcnZlci9tY3AuanMnO1xuaW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5pbXBvcnQgeyBTdHJpbmdDYXRhbG9nIH0gZnJvbSAnLi4vc3RyaW5nLWNhdGFsb2cnO1xuXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJMaXN0U3VwcG9ydGVkTGFuZ3VhZ2VzKHNlcnZlcjogTWNwU2VydmVyKSB7XG4gICAgc2VydmVyLnJlZ2lzdGVyVG9vbChcbiAgICAgICAgJ2xpc3Rfc3VwcG9ydGVkX2xhbmd1YWdlcycsXG4gICAgICAgIHtcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAgICAgICAgICdMaXN0IGFsbCBzdXBwb3J0ZWQgbGFuZ3VhZ2VzIGluIGEgZ2l2ZW4gWGNvZGUgU3RyaW5nIENhdGFsb2cgKC54Y3N0cmluZ3MpIGZpbGUuIFJldHVybnMgdGhlIHNvdXJjZSBsYW5ndWFnZSBhbmQgYWxsIGxhbmd1YWdlcyB0aGF0IGhhdmUgdHJhbnNsYXRpb25zLicsXG4gICAgICAgICAgICBpbnB1dFNjaGVtYToge1xuICAgICAgICAgICAgICAgIGZpbGVQYXRoOiB6LnN0cmluZygpLmRlc2NyaWJlKCdBYnNvbHV0ZSBwYXRoIHRvIHRoZSAueGNzdHJpbmdzIGZpbGUnKSxcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICAgIGFzeW5jICh7IGZpbGVQYXRoIH0pID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGNhdGFsb2cgPSBuZXcgU3RyaW5nQ2F0YWxvZyhmaWxlUGF0aCk7XG4gICAgICAgICAgICBjb25zdCBsYW5ndWFnZXMgPSBjYXRhbG9nLmdldFN1cHBvcnRlZExhbmd1YWdlcygpO1xuICAgICAgICAgICAgY29uc3Qgc291cmNlTGFuZ3VhZ2UgPSBjYXRhbG9nLmdldFNvdXJjZUxhbmd1YWdlKCk7XG5cbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgY29udGVudDogW1xuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAndGV4dCcgYXMgY29uc3QsXG4gICAgICAgICAgICAgICAgICAgICAgICB0ZXh0OiBKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZUxhbmd1YWdlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXBwb3J0ZWRMYW5ndWFnZXM6IGxhbmd1YWdlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnQ6IGxhbmd1YWdlcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBudWxsLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDJcbiAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICApO1xufVxuIl19
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSearchKeys = registerSearchKeys;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const string_catalog_1 = require("../string-catalog");
|
|
6
|
+
function registerSearchKeys(server) {
|
|
7
|
+
server.registerTool('search_keys', {
|
|
8
|
+
description: 'Search for localization keys containing a specific substring. Useful for finding keys when you only know part of the key name.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
filePath: zod_1.z.string().describe('Absolute path to the .xcstrings file'),
|
|
11
|
+
query: zod_1.z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('Substring to search for in key names (case-insensitive)'),
|
|
14
|
+
},
|
|
15
|
+
}, async ({ filePath, query }) => {
|
|
16
|
+
const catalog = new string_catalog_1.StringCatalog(filePath);
|
|
17
|
+
const keys = catalog.searchKeys(query);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: 'text',
|
|
22
|
+
text: JSON.stringify({
|
|
23
|
+
query,
|
|
24
|
+
matchingKeys: keys,
|
|
25
|
+
count: keys.length,
|
|
26
|
+
}, null, 2),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VhcmNoLWtleXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdG9vbHMvc2VhcmNoLWtleXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFJQSxnREFtQ0M7QUF0Q0QsNkJBQXdCO0FBQ3hCLHNEQUFrRDtBQUVsRCxTQUFnQixrQkFBa0IsQ0FBQyxNQUFpQjtJQUNoRCxNQUFNLENBQUMsWUFBWSxDQUNmLGFBQWEsRUFDYjtRQUNJLFdBQVcsRUFDUCxnSUFBZ0k7UUFDcEksV0FBVyxFQUFFO1lBQ1QsUUFBUSxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsc0NBQXNDLENBQUM7WUFDckUsS0FBSyxFQUFFLE9BQUM7aUJBQ0gsTUFBTSxFQUFFO2lCQUNSLFFBQVEsQ0FBQyx5REFBeUQsQ0FBQztTQUMzRTtLQUNKLEVBQ0QsS0FBSyxFQUFFLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUU7UUFDMUIsTUFBTSxPQUFPLEdBQUcsSUFBSSw4QkFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFdkMsT0FBTztZQUNILE9BQU8sRUFBRTtnQkFDTDtvQkFDSSxJQUFJLEVBQUUsTUFBZTtvQkFDckIsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQ2hCO3dCQUNJLEtBQUs7d0JBQ0wsWUFBWSxFQUFFLElBQUk7d0JBQ2xCLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTTtxQkFDckIsRUFDRCxJQUFJLEVBQ0osQ0FBQyxDQUNKO2lCQUNKO2FBQ0o7U0FDSixDQUFDO0lBQ04sQ0FBQyxDQUNKLENBQUM7QUFDTixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTWNwU2VydmVyIH0gZnJvbSAnQG1vZGVsY29udGV4dHByb3RvY29sL3Nkay9zZXJ2ZXIvbWNwLmpzJztcbmltcG9ydCB7IHogfSBmcm9tICd6b2QnO1xuaW1wb3J0IHsgU3RyaW5nQ2F0YWxvZyB9IGZyb20gJy4uL3N0cmluZy1jYXRhbG9nJztcblxuZXhwb3J0IGZ1bmN0aW9uIHJlZ2lzdGVyU2VhcmNoS2V5cyhzZXJ2ZXI6IE1jcFNlcnZlcikge1xuICAgIHNlcnZlci5yZWdpc3RlclRvb2woXG4gICAgICAgICdzZWFyY2hfa2V5cycsXG4gICAgICAgIHtcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAgICAgICAgICdTZWFyY2ggZm9yIGxvY2FsaXphdGlvbiBrZXlzIGNvbnRhaW5pbmcgYSBzcGVjaWZpYyBzdWJzdHJpbmcuIFVzZWZ1bCBmb3IgZmluZGluZyBrZXlzIHdoZW4geW91IG9ubHkga25vdyBwYXJ0IG9mIHRoZSBrZXkgbmFtZS4nLFxuICAgICAgICAgICAgaW5wdXRTY2hlbWE6IHtcbiAgICAgICAgICAgICAgICBmaWxlUGF0aDogei5zdHJpbmcoKS5kZXNjcmliZSgnQWJzb2x1dGUgcGF0aCB0byB0aGUgLnhjc3RyaW5ncyBmaWxlJyksXG4gICAgICAgICAgICAgICAgcXVlcnk6IHpcbiAgICAgICAgICAgICAgICAgICAgLnN0cmluZygpXG4gICAgICAgICAgICAgICAgICAgIC5kZXNjcmliZSgnU3Vic3RyaW5nIHRvIHNlYXJjaCBmb3IgaW4ga2V5IG5hbWVzIChjYXNlLWluc2Vuc2l0aXZlKScpLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgfSxcbiAgICAgICAgYXN5bmMgKHsgZmlsZVBhdGgsIHF1ZXJ5IH0pID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGNhdGFsb2cgPSBuZXcgU3RyaW5nQ2F0YWxvZyhmaWxlUGF0aCk7XG4gICAgICAgICAgICBjb25zdCBrZXlzID0gY2F0YWxvZy5zZWFyY2hLZXlzKHF1ZXJ5KTtcblxuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBjb250ZW50OiBbXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU6ICd0ZXh0JyBhcyBjb25zdCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRleHQ6IEpTT04uc3RyaW5naWZ5KFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hdGNoaW5nS2V5czoga2V5cyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnQ6IGtleXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAyXG4gICAgICAgICAgICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIF0sXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerUpdateTranslations = registerUpdateTranslations;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const string_catalog_1 = require("../string-catalog");
|
|
6
|
+
const toolDescription = `Update or add translations to a String Catalog. Accepts an array of translation entries.
|
|
7
|
+
|
|
8
|
+
IMPORTANT: iOS strings support format placeholders that must be preserved in translations:
|
|
9
|
+
- %@ for strings (objects)
|
|
10
|
+
- %d or %lld for integers
|
|
11
|
+
- %f for floating point numbers
|
|
12
|
+
- %1$@, %2$@ etc. for positional arguments (order can be changed in translations)
|
|
13
|
+
|
|
14
|
+
Example input:
|
|
15
|
+
{
|
|
16
|
+
"data": [
|
|
17
|
+
{
|
|
18
|
+
"key": "hello_world",
|
|
19
|
+
"translations": [
|
|
20
|
+
{ "language": "en", "value": "Hello World" },
|
|
21
|
+
{ "language": "de", "value": "Hallo Welt" },
|
|
22
|
+
{ "language": "no", "value": "Hei Verden" }
|
|
23
|
+
],
|
|
24
|
+
"comment": "Greeting message shown on home screen"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "items_count",
|
|
28
|
+
"translations": [
|
|
29
|
+
{ "language": "en", "value": "%lld items" },
|
|
30
|
+
{ "language": "de", "value": "%lld Elemente" }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}`;
|
|
35
|
+
const translationEntrySchema = zod_1.z.object({
|
|
36
|
+
language: zod_1.z.string().describe('Language code (e.g., "en", "de", "no", "vi")'),
|
|
37
|
+
value: zod_1.z
|
|
38
|
+
.string()
|
|
39
|
+
.describe('The translated text. Preserve any format placeholders like %@, %lld, %d'),
|
|
40
|
+
state: zod_1.z
|
|
41
|
+
.enum(['new', 'translated', 'needs_review', 'stale'])
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Translation state (defaults to "translated")'),
|
|
44
|
+
});
|
|
45
|
+
const dataEntrySchema = zod_1.z.object({
|
|
46
|
+
key: zod_1.z.string().describe('The localization key'),
|
|
47
|
+
translations: zod_1.z.array(translationEntrySchema).describe('Array of language translations'),
|
|
48
|
+
comment: zod_1.z.string().optional().describe('Optional comment describing the string context'),
|
|
49
|
+
});
|
|
50
|
+
const inputSchema = zod_1.z.object({
|
|
51
|
+
filePath: zod_1.z.string().describe('Absolute path to the .xcstrings file'),
|
|
52
|
+
data: zod_1.z.array(dataEntrySchema).describe('Array of translation entries to add or update'),
|
|
53
|
+
});
|
|
54
|
+
function registerUpdateTranslations(server) {
|
|
55
|
+
server.registerTool('update_translations', {
|
|
56
|
+
description: toolDescription,
|
|
57
|
+
inputSchema,
|
|
58
|
+
}, async ({ filePath, data }) => {
|
|
59
|
+
const catalog = new string_catalog_1.StringCatalog(filePath);
|
|
60
|
+
const result = catalog.updateTranslations(data);
|
|
61
|
+
catalog.save();
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: JSON.stringify({
|
|
67
|
+
success: true,
|
|
68
|
+
updatedKeys: result.updated,
|
|
69
|
+
createdKeys: result.created,
|
|
70
|
+
totalUpdated: result.updated.length,
|
|
71
|
+
totalCreated: result.created.length,
|
|
72
|
+
}, null, 2),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBkYXRlLXRyYW5zbGF0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90b29scy91cGRhdGUtdHJhbnNsYXRpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBd0RBLGdFQWdDQztBQXZGRCw2QkFBd0I7QUFDeEIsc0RBQWtEO0FBRWxELE1BQU0sZUFBZSxHQUFHOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0VBNEJ0QixDQUFDO0FBRUgsTUFBTSxzQkFBc0IsR0FBRyxPQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3BDLFFBQVEsRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLDhDQUE4QyxDQUFDO0lBQzdFLEtBQUssRUFBRSxPQUFDO1NBQ0gsTUFBTSxFQUFFO1NBQ1IsUUFBUSxDQUFDLHlFQUF5RSxDQUFDO0lBQ3hGLEtBQUssRUFBRSxPQUFDO1NBQ0gsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLFlBQVksRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7U0FDcEQsUUFBUSxFQUFFO1NBQ1YsUUFBUSxDQUFDLDhDQUE4QyxDQUFDO0NBQ2hFLENBQUMsQ0FBQztBQUVILE1BQU0sZUFBZSxHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0IsR0FBRyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsc0JBQXNCLENBQUM7SUFDaEQsWUFBWSxFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQyxRQUFRLENBQUMsZ0NBQWdDLENBQUM7SUFDeEYsT0FBTyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQUMsZ0RBQWdELENBQUM7Q0FDNUYsQ0FBQyxDQUFDO0FBRUgsTUFBTSxXQUFXLEdBQUcsT0FBQyxDQUFDLE1BQU0sQ0FBQztJQUN6QixRQUFRLEVBQUUsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsQ0FBQyxzQ0FBc0MsQ0FBQztJQUNyRSxJQUFJLEVBQUUsT0FBQyxDQUFDLEtBQUssQ0FBQyxlQUFlLENBQUMsQ0FBQyxRQUFRLENBQUMsK0NBQStDLENBQUM7Q0FDM0YsQ0FBQyxDQUFDO0FBRUgsU0FBZ0IsMEJBQTBCLENBQUMsTUFBaUI7SUFDeEQsTUFBTSxDQUFDLFlBQVksQ0FDZixxQkFBcUIsRUFDckI7UUFDSSxXQUFXLEVBQUUsZUFBZTtRQUM1QixXQUFXO0tBQ2QsRUFDRCxLQUFLLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRTtRQUN6QixNQUFNLE9BQU8sR0FBRyxJQUFJLDhCQUFhLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hELE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUVmLE9BQU87WUFDSCxPQUFPLEVBQUU7Z0JBQ0w7b0JBQ0ksSUFBSSxFQUFFLE1BQWU7b0JBQ3JCLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUNoQjt3QkFDSSxPQUFPLEVBQUUsSUFBSTt3QkFDYixXQUFXLEVBQUUsTUFBTSxDQUFDLE9BQU87d0JBQzNCLFdBQVcsRUFBRSxNQUFNLENBQUMsT0FBTzt3QkFDM0IsWUFBWSxFQUFFLE1BQU0sQ0FBQyxPQUFPLENBQUMsTUFBTTt3QkFDbkMsWUFBWSxFQUFFLE1BQU0sQ0FBQyxPQUFPLENBQUMsTUFBTTtxQkFDdEMsRUFDRCxJQUFJLEVBQ0osQ0FBQyxDQUNKO2lCQUNKO2FBQ0o7U0FDSixDQUFDO0lBQ04sQ0FBQyxDQUNKLENBQUM7QUFDTixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTWNwU2VydmVyIH0gZnJvbSAnQG1vZGVsY29udGV4dHByb3RvY29sL3Nkay9zZXJ2ZXIvbWNwLmpzJztcbmltcG9ydCB7IHogfSBmcm9tICd6b2QnO1xuaW1wb3J0IHsgU3RyaW5nQ2F0YWxvZyB9IGZyb20gJy4uL3N0cmluZy1jYXRhbG9nJztcblxuY29uc3QgdG9vbERlc2NyaXB0aW9uID0gYFVwZGF0ZSBvciBhZGQgdHJhbnNsYXRpb25zIHRvIGEgU3RyaW5nIENhdGFsb2cuIEFjY2VwdHMgYW4gYXJyYXkgb2YgdHJhbnNsYXRpb24gZW50cmllcy5cblxuSU1QT1JUQU5UOiBpT1Mgc3RyaW5ncyBzdXBwb3J0IGZvcm1hdCBwbGFjZWhvbGRlcnMgdGhhdCBtdXN0IGJlIHByZXNlcnZlZCBpbiB0cmFuc2xhdGlvbnM6XG4tICVAIGZvciBzdHJpbmdzIChvYmplY3RzKVxuLSAlZCBvciAlbGxkIGZvciBpbnRlZ2Vyc1xuLSAlZiBmb3IgZmxvYXRpbmcgcG9pbnQgbnVtYmVyc1xuLSAlMSRALCAlMiRAIGV0Yy4gZm9yIHBvc2l0aW9uYWwgYXJndW1lbnRzIChvcmRlciBjYW4gYmUgY2hhbmdlZCBpbiB0cmFuc2xhdGlvbnMpXG5cbkV4YW1wbGUgaW5wdXQ6XG57XG4gIFwiZGF0YVwiOiBbXG4gICAge1xuICAgICAgXCJrZXlcIjogXCJoZWxsb193b3JsZFwiLFxuICAgICAgXCJ0cmFuc2xhdGlvbnNcIjogW1xuICAgICAgICB7IFwibGFuZ3VhZ2VcIjogXCJlblwiLCBcInZhbHVlXCI6IFwiSGVsbG8gV29ybGRcIiB9LFxuICAgICAgICB7IFwibGFuZ3VhZ2VcIjogXCJkZVwiLCBcInZhbHVlXCI6IFwiSGFsbG8gV2VsdFwiIH0sXG4gICAgICAgIHsgXCJsYW5ndWFnZVwiOiBcIm5vXCIsIFwidmFsdWVcIjogXCJIZWkgVmVyZGVuXCIgfVxuICAgICAgXSxcbiAgICAgIFwiY29tbWVudFwiOiBcIkdyZWV0aW5nIG1lc3NhZ2Ugc2hvd24gb24gaG9tZSBzY3JlZW5cIlxuICAgIH0sXG4gICAge1xuICAgICAgXCJrZXlcIjogXCJpdGVtc19jb3VudFwiLFxuICAgICAgXCJ0cmFuc2xhdGlvbnNcIjogW1xuICAgICAgICB7IFwibGFuZ3VhZ2VcIjogXCJlblwiLCBcInZhbHVlXCI6IFwiJWxsZCBpdGVtc1wiIH0sXG4gICAgICAgIHsgXCJsYW5ndWFnZVwiOiBcImRlXCIsIFwidmFsdWVcIjogXCIlbGxkIEVsZW1lbnRlXCIgfVxuICAgICAgXVxuICAgIH1cbiAgXVxufWA7XG5cbmNvbnN0IHRyYW5zbGF0aW9uRW50cnlTY2hlbWEgPSB6Lm9iamVjdCh7XG4gICAgbGFuZ3VhZ2U6IHouc3RyaW5nKCkuZGVzY3JpYmUoJ0xhbmd1YWdlIGNvZGUgKGUuZy4sIFwiZW5cIiwgXCJkZVwiLCBcIm5vXCIsIFwidmlcIiknKSxcbiAgICB2YWx1ZTogelxuICAgICAgICAuc3RyaW5nKClcbiAgICAgICAgLmRlc2NyaWJlKCdUaGUgdHJhbnNsYXRlZCB0ZXh0LiBQcmVzZXJ2ZSBhbnkgZm9ybWF0IHBsYWNlaG9sZGVycyBsaWtlICVALCAlbGxkLCAlZCcpLFxuICAgIHN0YXRlOiB6XG4gICAgICAgIC5lbnVtKFsnbmV3JywgJ3RyYW5zbGF0ZWQnLCAnbmVlZHNfcmV2aWV3JywgJ3N0YWxlJ10pXG4gICAgICAgIC5vcHRpb25hbCgpXG4gICAgICAgIC5kZXNjcmliZSgnVHJhbnNsYXRpb24gc3RhdGUgKGRlZmF1bHRzIHRvIFwidHJhbnNsYXRlZFwiKScpLFxufSk7XG5cbmNvbnN0IGRhdGFFbnRyeVNjaGVtYSA9IHoub2JqZWN0KHtcbiAgICBrZXk6IHouc3RyaW5nKCkuZGVzY3JpYmUoJ1RoZSBsb2NhbGl6YXRpb24ga2V5JyksXG4gICAgdHJhbnNsYXRpb25zOiB6LmFycmF5KHRyYW5zbGF0aW9uRW50cnlTY2hlbWEpLmRlc2NyaWJlKCdBcnJheSBvZiBsYW5ndWFnZSB0cmFuc2xhdGlvbnMnKSxcbiAgICBjb21tZW50OiB6LnN0cmluZygpLm9wdGlvbmFsKCkuZGVzY3JpYmUoJ09wdGlvbmFsIGNvbW1lbnQgZGVzY3JpYmluZyB0aGUgc3RyaW5nIGNvbnRleHQnKSxcbn0pO1xuXG5jb25zdCBpbnB1dFNjaGVtYSA9IHoub2JqZWN0KHtcbiAgICBmaWxlUGF0aDogei5zdHJpbmcoKS5kZXNjcmliZSgnQWJzb2x1dGUgcGF0aCB0byB0aGUgLnhjc3RyaW5ncyBmaWxlJyksXG4gICAgZGF0YTogei5hcnJheShkYXRhRW50cnlTY2hlbWEpLmRlc2NyaWJlKCdBcnJheSBvZiB0cmFuc2xhdGlvbiBlbnRyaWVzIHRvIGFkZCBvciB1cGRhdGUnKSxcbn0pO1xuXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJVcGRhdGVUcmFuc2xhdGlvbnMoc2VydmVyOiBNY3BTZXJ2ZXIpIHtcbiAgICBzZXJ2ZXIucmVnaXN0ZXJUb29sKFxuICAgICAgICAndXBkYXRlX3RyYW5zbGF0aW9ucycsXG4gICAgICAgIHtcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uOiB0b29sRGVzY3JpcHRpb24sXG4gICAgICAgICAgICBpbnB1dFNjaGVtYSxcbiAgICAgICAgfSxcbiAgICAgICAgYXN5bmMgKHsgZmlsZVBhdGgsIGRhdGEgfSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgY2F0YWxvZyA9IG5ldyBTdHJpbmdDYXRhbG9nKGZpbGVQYXRoKTtcbiAgICAgICAgICAgIGNvbnN0IHJlc3VsdCA9IGNhdGFsb2cudXBkYXRlVHJhbnNsYXRpb25zKGRhdGEpO1xuICAgICAgICAgICAgY2F0YWxvZy5zYXZlKCk7XG5cbiAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgY29udGVudDogW1xuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAndGV4dCcgYXMgY29uc3QsXG4gICAgICAgICAgICAgICAgICAgICAgICB0ZXh0OiBKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVwZGF0ZWRLZXlzOiByZXN1bHQudXBkYXRlZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JlYXRlZEtleXM6IHJlc3VsdC5jcmVhdGVkLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbFVwZGF0ZWQ6IHJlc3VsdC51cGRhdGVkLmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxDcmVhdGVkOiByZXN1bHQuY3JlYXRlZC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBudWxsLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDJcbiAgICAgICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICApO1xufVxuIl19
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Xcode String Catalog (.xcstrings) file format
|
|
3
|
+
*/
|
|
4
|
+
/** State of a localization string */
|
|
5
|
+
export type LocalizationState = 'new' | 'translated' | 'needs_review' | 'stale';
|
|
6
|
+
/** A single string unit containing the translation value and state */
|
|
7
|
+
export interface StringUnit {
|
|
8
|
+
state: LocalizationState;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
/** Localization entry for a single language */
|
|
12
|
+
export interface LocalizationEntry {
|
|
13
|
+
stringUnit?: StringUnit;
|
|
14
|
+
/** For plural variations */
|
|
15
|
+
variations?: {
|
|
16
|
+
plural?: {
|
|
17
|
+
zero?: {
|
|
18
|
+
stringUnit: StringUnit;
|
|
19
|
+
};
|
|
20
|
+
one?: {
|
|
21
|
+
stringUnit: StringUnit;
|
|
22
|
+
};
|
|
23
|
+
two?: {
|
|
24
|
+
stringUnit: StringUnit;
|
|
25
|
+
};
|
|
26
|
+
few?: {
|
|
27
|
+
stringUnit: StringUnit;
|
|
28
|
+
};
|
|
29
|
+
many?: {
|
|
30
|
+
stringUnit: StringUnit;
|
|
31
|
+
};
|
|
32
|
+
other?: {
|
|
33
|
+
stringUnit: StringUnit;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
device?: Record<string, {
|
|
37
|
+
stringUnit: StringUnit;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** All localizations for a single string key */
|
|
42
|
+
export interface StringLocalizations {
|
|
43
|
+
[languageCode: string]: LocalizationEntry;
|
|
44
|
+
}
|
|
45
|
+
/** A single string entry in the catalog */
|
|
46
|
+
export interface StringEntry {
|
|
47
|
+
comment?: string;
|
|
48
|
+
extractionState?: 'manual' | 'extracted_with_value' | 'stale';
|
|
49
|
+
localizations?: StringLocalizations;
|
|
50
|
+
}
|
|
51
|
+
/** Root structure of an .xcstrings file */
|
|
52
|
+
export interface XCStrings {
|
|
53
|
+
sourceLanguage: string;
|
|
54
|
+
version?: string;
|
|
55
|
+
strings: {
|
|
56
|
+
[key: string]: StringEntry;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Translation input format for the update tool
|
|
61
|
+
*/
|
|
62
|
+
export interface TranslationInput {
|
|
63
|
+
key: string;
|
|
64
|
+
translations: {
|
|
65
|
+
language: string;
|
|
66
|
+
value: string;
|
|
67
|
+
state?: LocalizationState;
|
|
68
|
+
}[];
|
|
69
|
+
comment?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface TranslationData {
|
|
72
|
+
data: TranslationInput[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Output format for listing translations of a key
|
|
76
|
+
*/
|
|
77
|
+
export interface KeyTranslation {
|
|
78
|
+
language: string;
|
|
79
|
+
value: string;
|
|
80
|
+
state: LocalizationState;
|
|
81
|
+
}
|
|
82
|
+
export interface KeyTranslationsResult {
|
|
83
|
+
key: string;
|
|
84
|
+
sourceLanguage: string;
|
|
85
|
+
translations: KeyTranslation[];
|
|
86
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for Xcode String Catalog (.xcstrings) file format
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOztHQUVHIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUeXBlcyBmb3IgWGNvZGUgU3RyaW5nIENhdGFsb2cgKC54Y3N0cmluZ3MpIGZpbGUgZm9ybWF0XG4gKi9cblxuLyoqIFN0YXRlIG9mIGEgbG9jYWxpemF0aW9uIHN0cmluZyAqL1xuZXhwb3J0IHR5cGUgTG9jYWxpemF0aW9uU3RhdGUgPSAnbmV3JyB8ICd0cmFuc2xhdGVkJyB8ICduZWVkc19yZXZpZXcnIHwgJ3N0YWxlJztcblxuLyoqIEEgc2luZ2xlIHN0cmluZyB1bml0IGNvbnRhaW5pbmcgdGhlIHRyYW5zbGF0aW9uIHZhbHVlIGFuZCBzdGF0ZSAqL1xuZXhwb3J0IGludGVyZmFjZSBTdHJpbmdVbml0IHtcbiAgICBzdGF0ZTogTG9jYWxpemF0aW9uU3RhdGU7XG4gICAgdmFsdWU6IHN0cmluZztcbn1cblxuLyoqIExvY2FsaXphdGlvbiBlbnRyeSBmb3IgYSBzaW5nbGUgbGFuZ3VhZ2UgKi9cbmV4cG9ydCBpbnRlcmZhY2UgTG9jYWxpemF0aW9uRW50cnkge1xuICAgIHN0cmluZ1VuaXQ/OiBTdHJpbmdVbml0O1xuICAgIC8qKiBGb3IgcGx1cmFsIHZhcmlhdGlvbnMgKi9cbiAgICB2YXJpYXRpb25zPzoge1xuICAgICAgICBwbHVyYWw/OiB7XG4gICAgICAgICAgICB6ZXJvPzogeyBzdHJpbmdVbml0OiBTdHJpbmdVbml0IH07XG4gICAgICAgICAgICBvbmU/OiB7IHN0cmluZ1VuaXQ6IFN0cmluZ1VuaXQgfTtcbiAgICAgICAgICAgIHR3bz86IHsgc3RyaW5nVW5pdDogU3RyaW5nVW5pdCB9O1xuICAgICAgICAgICAgZmV3PzogeyBzdHJpbmdVbml0OiBTdHJpbmdVbml0IH07XG4gICAgICAgICAgICBtYW55PzogeyBzdHJpbmdVbml0OiBTdHJpbmdVbml0IH07XG4gICAgICAgICAgICBvdGhlcj86IHsgc3RyaW5nVW5pdDogU3RyaW5nVW5pdCB9O1xuICAgICAgICB9O1xuICAgICAgICBkZXZpY2U/OiBSZWNvcmQ8c3RyaW5nLCB7IHN0cmluZ1VuaXQ6IFN0cmluZ1VuaXQgfT47XG4gICAgfTtcbn1cblxuLyoqIEFsbCBsb2NhbGl6YXRpb25zIGZvciBhIHNpbmdsZSBzdHJpbmcga2V5ICovXG5leHBvcnQgaW50ZXJmYWNlIFN0cmluZ0xvY2FsaXphdGlvbnMge1xuICAgIFtsYW5ndWFnZUNvZGU6IHN0cmluZ106IExvY2FsaXphdGlvbkVudHJ5O1xufVxuXG4vKiogQSBzaW5nbGUgc3RyaW5nIGVudHJ5IGluIHRoZSBjYXRhbG9nICovXG5leHBvcnQgaW50ZXJmYWNlIFN0cmluZ0VudHJ5IHtcbiAgICBjb21tZW50Pzogc3RyaW5nO1xuICAgIGV4dHJhY3Rpb25TdGF0ZT86ICdtYW51YWwnIHwgJ2V4dHJhY3RlZF93aXRoX3ZhbHVlJyB8ICdzdGFsZSc7XG4gICAgbG9jYWxpemF0aW9ucz86IFN0cmluZ0xvY2FsaXphdGlvbnM7XG59XG5cbi8qKiBSb290IHN0cnVjdHVyZSBvZiBhbiAueGNzdHJpbmdzIGZpbGUgKi9cbmV4cG9ydCBpbnRlcmZhY2UgWENTdHJpbmdzIHtcbiAgICBzb3VyY2VMYW5ndWFnZTogc3RyaW5nO1xuICAgIHZlcnNpb24/OiBzdHJpbmc7XG4gICAgc3RyaW5nczoge1xuICAgICAgICBba2V5OiBzdHJpbmddOiBTdHJpbmdFbnRyeTtcbiAgICB9O1xufVxuXG4vKipcbiAqIFRyYW5zbGF0aW9uIGlucHV0IGZvcm1hdCBmb3IgdGhlIHVwZGF0ZSB0b29sXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVHJhbnNsYXRpb25JbnB1dCB7XG4gICAga2V5OiBzdHJpbmc7XG4gICAgdHJhbnNsYXRpb25zOiB7XG4gICAgICAgIGxhbmd1YWdlOiBzdHJpbmc7XG4gICAgICAgIHZhbHVlOiBzdHJpbmc7XG4gICAgICAgIHN0YXRlPzogTG9jYWxpemF0aW9uU3RhdGU7XG4gICAgfVtdO1xuICAgIGNvbW1lbnQ/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVHJhbnNsYXRpb25EYXRhIHtcbiAgICBkYXRhOiBUcmFuc2xhdGlvbklucHV0W107XG59XG5cbi8qKlxuICogT3V0cHV0IGZvcm1hdCBmb3IgbGlzdGluZyB0cmFuc2xhdGlvbnMgb2YgYSBrZXlcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBLZXlUcmFuc2xhdGlvbiB7XG4gICAgbGFuZ3VhZ2U6IHN0cmluZztcbiAgICB2YWx1ZTogc3RyaW5nO1xuICAgIHN0YXRlOiBMb2NhbGl6YXRpb25TdGF0ZTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBLZXlUcmFuc2xhdGlvbnNSZXN1bHQge1xuICAgIGtleTogc3RyaW5nO1xuICAgIHNvdXJjZUxhbmd1YWdlOiBzdHJpbmc7XG4gICAgdHJhbnNsYXRpb25zOiBLZXlUcmFuc2xhdGlvbltdO1xufVxuIl19
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
eslint.configs.recommended,
|
|
6
|
+
...tseslint.configs.recommended,
|
|
7
|
+
{
|
|
8
|
+
files: ['src/**/*.ts'],
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parserOptions: {
|
|
11
|
+
project: './tsconfig.json',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
16
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
17
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
ignores: ['dist/**', 'node_modules/**'],
|
|
22
|
+
}
|
|
23
|
+
);
|
package/images/mcp.jpeg
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "string-catalog-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server for interacting with Xcode String Catalog (.xcstrings) files",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"string-catalog-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"dev": "tsx src/index.ts",
|
|
15
|
+
"inspect": "pnpm dlx @modelcontextprotocol/inspector tsx src/index.ts",
|
|
16
|
+
"mcp:add": "claude mcp add string-catalog-mcp -- pnpm --dir=$PWD dlx tsx $PWD/src/index.ts",
|
|
17
|
+
"mcp:remove": "claude mcp remove string-catalog-mcp",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint src/**/*.ts",
|
|
20
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
21
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
22
|
+
"format:check": "prettier --check \"src/**/*.ts\""
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"xcode",
|
|
27
|
+
"string-catalog",
|
|
28
|
+
"xcstrings",
|
|
29
|
+
"localization",
|
|
30
|
+
"i18n"
|
|
31
|
+
],
|
|
32
|
+
"author": "Khoa Pham",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
36
|
+
"zod": "^4.2.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^9.39.2",
|
|
40
|
+
"@types/node": "^20.10.0",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.50.0",
|
|
43
|
+
"eslint": "^9.39.2",
|
|
44
|
+
"prettier": "^3.4.2",
|
|
45
|
+
"tsx": "^4.19.0",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"typescript-eslint": "^8.50.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { registerAllTools } from './mcp/tools/index';
|
|
6
|
+
import { registerAllPrompts } from './mcp/prompts/index';
|
|
7
|
+
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: 'string-catalog-mcp',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
registerAllTools(server);
|
|
14
|
+
registerAllPrompts(server);
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
await server.connect(transport);
|
|
19
|
+
console.error('String Catalog MCP Server running on stdio');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main().catch((error) => {
|
|
23
|
+
console.error('Fatal error:', error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerBatchTranslatePrompt(server: McpServer) {
|
|
5
|
+
server.registerPrompt(
|
|
6
|
+
'batch-translate',
|
|
7
|
+
{
|
|
8
|
+
description: 'Translate all untranslated or stale strings in a catalog for specified languages.',
|
|
9
|
+
argsSchema: {
|
|
10
|
+
filePath: z.string().describe('Absolute path to the .xcstrings file'),
|
|
11
|
+
targetLanguages: z.string().describe('Comma-separated list of target language codes (e.g., "de,fr,ja")'),
|
|
12
|
+
includeStale: z.boolean().default(false).describe('Whether to re-translate stale entries'),
|
|
13
|
+
batchSize: z.number().default(20).describe('Number of keys to translate per batch (default: 20)'),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
async ({ filePath, targetLanguages, includeStale, batchSize }) => {
|
|
17
|
+
const targetLangList = targetLanguages.split(',').map(l => l.trim()).filter(Boolean);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
role: 'user',
|
|
23
|
+
content: {
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `# Batch Translation Request
|
|
26
|
+
|
|
27
|
+
## String Catalog File
|
|
28
|
+
${filePath}
|
|
29
|
+
|
|
30
|
+
## Target Languages
|
|
31
|
+
${targetLangList.map(l => `- ${l}`).join('\n')}
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
- Include stale translations: ${includeStale ? 'Yes' : 'No'}
|
|
35
|
+
- Batch size: ${batchSize} keys per batch
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
### Step 1: Analyze the Catalog
|
|
40
|
+
Use \`get_catalog_statistics\` to understand the current translation coverage.
|
|
41
|
+
|
|
42
|
+
### Step 2: Identify Keys Needing Translation
|
|
43
|
+
Use \`search_keys\` or \`list_all_keys\` to find:
|
|
44
|
+
- Keys with missing translations for target languages
|
|
45
|
+
${includeStale ? '- Keys with stale translations that need updating' : ''}
|
|
46
|
+
|
|
47
|
+
### Step 3: Translate in Batches
|
|
48
|
+
For each batch of up to ${batchSize} keys:
|
|
49
|
+
1. Get the source text using \`get_translations_for_key\`
|
|
50
|
+
2. Translate to all target languages
|
|
51
|
+
3. Prepare the JSON payload for \`update_translations\`
|
|
52
|
+
|
|
53
|
+
## iOS Format Placeholders Reference
|
|
54
|
+
Preserve these placeholders in translations:
|
|
55
|
+
- \`%@\` - String (object)
|
|
56
|
+
- \`%d\` / \`%lld\` - Integer
|
|
57
|
+
- \`%f\` - Float
|
|
58
|
+
- \`%1$@\`, \`%2$@\` - Positional (can reorder for grammar)
|
|
59
|
+
|
|
60
|
+
## Output Format
|
|
61
|
+
For each batch, provide:
|
|
62
|
+
|
|
63
|
+
\`\`\`json
|
|
64
|
+
{
|
|
65
|
+
"data": [
|
|
66
|
+
{
|
|
67
|
+
"key": "key_name",
|
|
68
|
+
"translations": [
|
|
69
|
+
{ "language": "de", "value": "German translation" },
|
|
70
|
+
{ "language": "fr", "value": "French translation" }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
## Instructions
|
|
78
|
+
1. Start by analyzing the catalog
|
|
79
|
+
2. Identify which keys need translation
|
|
80
|
+
3. Process keys in batches
|
|
81
|
+
4. After each batch, use \`update_translations\` to save
|
|
82
|
+
5. Report progress after each batch
|
|
83
|
+
|
|
84
|
+
Begin the batch translation process now.`,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerTranslateStringsPrompt } from './translate-strings';
|
|
3
|
+
import { registerReviewTranslationsPrompt } from './review-translations';
|
|
4
|
+
import { registerBatchTranslatePrompt } from './batch-translate';
|
|
5
|
+
|
|
6
|
+
export function registerAllPrompts(server: McpServer) {
|
|
7
|
+
registerTranslateStringsPrompt(server);
|
|
8
|
+
registerReviewTranslationsPrompt(server);
|
|
9
|
+
registerBatchTranslatePrompt(server);
|
|
10
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerReviewTranslationsPrompt(server: McpServer) {
|
|
5
|
+
server.registerPrompt(
|
|
6
|
+
'review-translations',
|
|
7
|
+
{
|
|
8
|
+
description: 'Review existing translations for quality, consistency, and proper placeholder usage.',
|
|
9
|
+
argsSchema: {
|
|
10
|
+
filePath: z.string().describe('Absolute path to the .xcstrings file'),
|
|
11
|
+
languages: z.string().optional().describe('Comma-separated list of language codes to review (default: all)'),
|
|
12
|
+
focusAreas: z.string().optional().describe('Comma-separated areas to focus on (e.g., "placeholders,consistency,tone")'),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
async ({ filePath, languages, focusAreas }) => {
|
|
16
|
+
const langSection = languages
|
|
17
|
+
? `\n## Languages to Review\n${languages.split(',').map(l => `- ${l.trim()}`).join('\n')}\n`
|
|
18
|
+
: '\n## Languages to Review\nAll available languages in the catalog.\n';
|
|
19
|
+
|
|
20
|
+
const focusSection = focusAreas
|
|
21
|
+
? `\n## Focus Areas\n${focusAreas.split(',').map(f => `- ${f.trim()}`).join('\n')}\n`
|
|
22
|
+
: '';
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
messages: [
|
|
26
|
+
{
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: {
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: `# Translation Review Request
|
|
31
|
+
|
|
32
|
+
## String Catalog File
|
|
33
|
+
${filePath}
|
|
34
|
+
${langSection}${focusSection}
|
|
35
|
+
## Review Checklist
|
|
36
|
+
|
|
37
|
+
### 1. Format Placeholder Verification
|
|
38
|
+
- Ensure all \`%@\`, \`%d\`, \`%lld\`, \`%f\` placeholders are preserved
|
|
39
|
+
- Verify positional arguments (\`%1$@\`, \`%2$@\`) are used correctly
|
|
40
|
+
- Check that placeholder count matches the source string
|
|
41
|
+
|
|
42
|
+
### 2. Translation Quality
|
|
43
|
+
- Verify translations are accurate and natural-sounding
|
|
44
|
+
- Check for grammatical errors
|
|
45
|
+
- Ensure translations fit the context of a mobile app UI
|
|
46
|
+
|
|
47
|
+
### 3. Consistency
|
|
48
|
+
- Similar strings should have consistent translations
|
|
49
|
+
- Terminology should be uniform across the app
|
|
50
|
+
- UI element names should match platform conventions
|
|
51
|
+
|
|
52
|
+
### 4. Cultural Appropriateness
|
|
53
|
+
- Verify idioms are properly localized
|
|
54
|
+
- Check for culturally sensitive content
|
|
55
|
+
- Ensure date/number formats are appropriate
|
|
56
|
+
|
|
57
|
+
### 5. Length Considerations
|
|
58
|
+
- Flag translations that are significantly longer than source
|
|
59
|
+
- Consider UI space constraints for mobile apps
|
|
60
|
+
|
|
61
|
+
## Instructions
|
|
62
|
+
1. Use the \`get_catalog_statistics\` tool to see overall translation coverage
|
|
63
|
+
2. Use the \`list_all_keys\` tool to see available keys
|
|
64
|
+
3. Use the \`get_translations_for_key\` tool to examine specific translations
|
|
65
|
+
4. Report any issues found with specific keys and languages
|
|
66
|
+
5. Suggest corrections using the \`update_translations\` tool format
|
|
67
|
+
|
|
68
|
+
## Output Format
|
|
69
|
+
Provide a structured review report:
|
|
70
|
+
- Summary of findings
|
|
71
|
+
- List of issues by severity (critical, warning, suggestion)
|
|
72
|
+
- Recommended fixes in JSON format for the update_translations tool`,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function registerTranslateStringsPrompt(server: McpServer) {
|
|
5
|
+
server.registerPrompt(
|
|
6
|
+
'translate-strings',
|
|
7
|
+
{
|
|
8
|
+
description: 'Generate translations for iOS string catalog keys. Provides guidance on format placeholders and returns structured JSON for the update_translations tool.',
|
|
9
|
+
argsSchema: {
|
|
10
|
+
keys: z.string().describe('Comma-separated list of keys to translate (e.g., "hello_world,goodbye,welcome_message")'),
|
|
11
|
+
sourceLanguage: z.string().default('en').describe('Source language code (default: en)'),
|
|
12
|
+
targetLanguages: z.string().describe('Comma-separated list of target language codes (e.g., "de,fr,ja,zh-Hans")'),
|
|
13
|
+
context: z.string().optional().describe('Optional context about where these strings are used in the app'),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
async ({ keys, sourceLanguage, targetLanguages, context }) => {
|
|
17
|
+
const keyList = keys.split(',').map(k => k.trim()).filter(Boolean);
|
|
18
|
+
const targetLangList = targetLanguages.split(',').map(l => l.trim()).filter(Boolean);
|
|
19
|
+
|
|
20
|
+
const contextSection = context
|
|
21
|
+
? `\n## Context\nThese strings are used in: ${context}\n`
|
|
22
|
+
: '';
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
messages: [
|
|
26
|
+
{
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: {
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: `# Translation Request for iOS String Catalog
|
|
31
|
+
|
|
32
|
+
## Keys to Translate
|
|
33
|
+
${keyList.map(k => `- "${k}"`).join('\n')}
|
|
34
|
+
|
|
35
|
+
## Source Language
|
|
36
|
+
${sourceLanguage}
|
|
37
|
+
|
|
38
|
+
## Target Languages
|
|
39
|
+
${targetLangList.map(l => `- ${l}`).join('\n')}
|
|
40
|
+
${contextSection}
|
|
41
|
+
## iOS Format Placeholders
|
|
42
|
+
When translating, preserve these iOS format placeholders exactly as they appear:
|
|
43
|
+
- \`%@\` - String placeholder (objects)
|
|
44
|
+
- \`%d\` or \`%lld\` - Integer placeholder
|
|
45
|
+
- \`%f\` - Floating point number placeholder
|
|
46
|
+
- \`%1$@\`, \`%2$@\` - Positional arguments (order CAN be changed to fit natural language grammar)
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
If source is: "Hello %@, you have %lld items"
|
|
50
|
+
German could be: "Hallo %@, Sie haben %lld Artikel"
|
|
51
|
+
Japanese could be: "%@さん、%lld個のアイテムがあります"
|
|
52
|
+
|
|
53
|
+
## Instructions
|
|
54
|
+
1. Translate each key into all target languages
|
|
55
|
+
2. Preserve all format placeholders
|
|
56
|
+
3. Ensure translations sound natural in each language
|
|
57
|
+
4. Consider cultural context and localization best practices
|
|
58
|
+
|
|
59
|
+
## Required Output Format
|
|
60
|
+
Return the translations as JSON that can be used with the \`update_translations\` tool:
|
|
61
|
+
|
|
62
|
+
\`\`\`json
|
|
63
|
+
{
|
|
64
|
+
"data": [
|
|
65
|
+
{
|
|
66
|
+
"key": "key_name",
|
|
67
|
+
"translations": [
|
|
68
|
+
{ "language": "en", "value": "English text" },
|
|
69
|
+
{ "language": "de", "value": "German text" },
|
|
70
|
+
{ "language": "fr", "value": "French text" }
|
|
71
|
+
],
|
|
72
|
+
"comment": "Optional: describe where this string is used"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
Please translate the keys now.`,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|