@syntholoc/mcp 0.1.2 → 0.2.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.
@@ -2,7 +2,7 @@ import {
2
2
  TOOL_SET_NAMES,
3
3
  buildMcpServer,
4
4
  parseList
5
- } from "./chunk-QOVGANRN.js";
5
+ } from "./chunk-DZKWHV2O.js";
6
6
  export {
7
7
  TOOL_SET_NAMES,
8
8
  buildMcpServer,
@@ -49,10 +49,15 @@ function registerPrompts(server) {
49
49
  "1. Read `.syntholoc/config.json` for the source file(s) and project.",
50
50
  projectId ? ` (Use project ${projectId} if config is missing.)` : "",
51
51
  "2. From `git diff` on the source file(s), collect added/changed keys and `upsert_keys` (dryRun first to preview).",
52
+ " - MULTI-FILE / namespaced project (a file per namespace, e.g. messages/en/home.json): import each",
53
+ " changed file with `import_source_strings` passing `keyPrefix` = the namespace (the filename",
54
+ ' without extension, e.g. "home"). This keeps overlapping keys from different files distinct.',
52
55
  "3. Call `get_config` for `maxBulkSize`. For EACH target locale check `locale_progress` \u2192 `untranslated`.",
53
56
  " - If untranslated >= maxBulkSize: `translate_locale` (scope UNTRANSLATED) and `wait_for_job`.",
54
57
  " - If below the threshold: tell me the counts and ASK whether to translate now or accumulate.",
55
58
  "4. `export_locale` for each translated locale and write the result back into the mapped local files.",
59
+ " - Namespaced project: call `export_locale` once per namespace with the same `keyPrefix` (the prefix",
60
+ " is stripped on export) and write each result to its file, e.g. messages/{locale}/home.json.",
56
61
  "5. Summarize keys added, locales translated, and the cost from the job usage."
57
62
  ].filter(Boolean).join("\n")
58
63
  )
@@ -84,10 +89,21 @@ function registerPrompts(server) {
84
89
  () => userMessage(
85
90
  [
86
91
  "Set up Syntholoc for this repository:",
87
- "1. Find the i18n source files (e.g. messages/en.json, locales/en/*.json) and infer the source locale.",
88
- "2. `create_project`, then `seed_starter_locales` (or `add_locale`) for the target languages.",
89
- "3. `import_source_strings` from the source file(s).",
90
- "4. Write `.syntholoc/config.json` mapping source \u2192 translation file patterns and the project id.",
92
+ "1. Find the i18n source files and infer the source locale. Note the LAYOUT:",
93
+ " - Single file per locale (e.g. messages/en.json) \u2192 one mapping, no namespace.",
94
+ " - A directory per locale with many namespace files (e.g. messages/en/home.json, common.json) \u2192",
95
+ " each file is a NAMESPACE. Syntholoc stores a flat key space, so import each file under a",
96
+ " `keyPrefix` = its namespace to avoid collisions between files.",
97
+ "2. `create_project`, then `seed_starter_locales` (or `add_locale`) for the target languages. Use valid",
98
+ ' BCP-47 codes (e.g. "zh-CN", not "cn"); `add_locale` accepts `code` or `locale`.',
99
+ "3. Import the source strings:",
100
+ " - Single file \u2192 `import_source_strings` once.",
101
+ " - Namespaced \u2192 `import_source_strings` once PER FILE with `keyPrefix` set to the namespace",
102
+ " (filename without extension). Keys become e.g. `home.title`, `common.title`.",
103
+ "4. Write `.syntholoc/config.json` (schema https://syntholoc.com/schemas/config.json): the `projectId` and",
104
+ " a Crowdin-style `files` array of `{ source, translation }` patterns with a `{locale}` placeholder,",
105
+ ' e.g. { "source": "messages/en/home.json", "translation": "messages/{locale}/home.json", "namespace": "home" }.',
106
+ ' Do NOT invent fields like `i18nDir`, `format: "nested-dirs"`, or `localeMapping` \u2014 they are not read.',
91
107
  "5. Offer to run the localize_changed flow next."
92
108
  ].join("\n")
93
109
  )
@@ -575,17 +591,22 @@ function registerKeyTools(server, gql) {
575
591
  "import_source_strings",
576
592
  {
577
593
  title: "Import source strings",
578
- description: "Imports source strings from a file (paste the raw content) and returns the diff summary. Use `dryRun` to preview. For a structured delta prefer upsert_keys.",
594
+ description: 'Imports source strings from ONE file (paste the raw content) and returns the diff summary. For a multi-file / namespaced project (a file per namespace, e.g. messages/en/home.json), pass `keyPrefix` (the namespace, e.g. "home") so keys become `home.title` and files with overlapping top-level keys do not collide \u2014 call once per file. Use `dryRun` to preview.',
579
595
  inputSchema: {
580
596
  projectId: z6.string(),
581
597
  content: z6.string(),
582
598
  format: z6.enum(FORMATS),
599
+ keyPrefix: z6.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
600
+ sourceFile: z6.string().optional().describe('Real source file path, e.g. "messages/en/home.json" \u2014 stored for the file tree + reliable export.'),
583
601
  dryRun: z6.boolean().default(false)
584
602
  },
585
603
  annotations: { idempotentHint: true, openWorldHint: true }
586
604
  },
587
- async ({ projectId, content, format, dryRun }) => jsonResult(
588
- (await gql.request(IMPORT_SOURCE_STRINGS, { projectId, input: { content, format, dryRun } })).importSourceStrings
605
+ async ({ projectId, content, format, keyPrefix, sourceFile, dryRun }) => jsonResult(
606
+ (await gql.request(IMPORT_SOURCE_STRINGS, {
607
+ projectId,
608
+ input: { content, format, keyPrefix, sourceFile, dryRun }
609
+ })).importSourceStrings
589
610
  )
590
611
  );
591
612
  }
@@ -608,10 +629,13 @@ function registerLocaleTools(server, gql) {
608
629
  "add_locale",
609
630
  {
610
631
  title: "Add locale",
611
- description: "Adds a target locale (BCP-47 code) to a project.",
632
+ description: "Adds ONE target locale to a project. To add several at once, prefer seed_starter_locales (one call for the whole list).",
612
633
  inputSchema: {
613
634
  projectId: z7.string(),
614
- code: z7.string().describe('BCP-47 code, e.g. "de-DE"'),
635
+ // `code` and `locale` are aliases — accept either so the natural
636
+ // `locale: "de-DE"` form works as well as `code`.
637
+ code: z7.string().optional().describe('BCP-47 code, e.g. "de-DE" or "ru". Alias: `locale`.'),
638
+ locale: z7.string().optional().describe("Alias for `code`."),
615
639
  name: z7.string().optional(),
616
640
  formality: z7.string().optional(),
617
641
  gender: z7.string().optional(),
@@ -619,14 +643,26 @@ function registerLocaleTools(server, gql) {
619
643
  },
620
644
  annotations: { openWorldHint: true }
621
645
  },
622
- async ({ projectId, ...input }) => jsonResult((await gql.request(ADD_LOCALE, { projectId, input })).addLocale)
646
+ async ({ projectId, code, locale, name, formality, gender, variant }) => {
647
+ const resolvedCode = code ?? locale;
648
+ if (!resolvedCode) {
649
+ return jsonResult({
650
+ error: 'Missing locale code. Pass `code` (BCP-47, e.g. "de-DE" or "ru"). To add several locales at once, use seed_starter_locales with a `codes` array.'
651
+ });
652
+ }
653
+ const input = { code: resolvedCode, name, formality, gender, variant };
654
+ return jsonResult((await gql.request(ADD_LOCALE, { projectId, input })).addLocale);
655
+ }
623
656
  );
624
657
  server.registerTool(
625
658
  "seed_starter_locales",
626
659
  {
627
660
  title: "Seed starter locales",
628
- description: "Adds a set of common starter locales to a project (omit codes for the defaults).",
629
- inputSchema: { projectId: z7.string(), codes: z7.array(z7.string()).optional() },
661
+ description: 'Adds MULTIPLE target locales in one call \u2014 pass `codes` (BCP-47, e.g. ["ru","de-DE","fr"]), or omit for a common starter set. Prefer this over repeated add_locale calls.',
662
+ inputSchema: {
663
+ projectId: z7.string(),
664
+ codes: z7.array(z7.string()).optional().describe('BCP-47 codes to add, e.g. ["ru","de-DE","fr"].')
665
+ },
630
666
  annotations: { openWorldHint: true }
631
667
  },
632
668
  async ({ projectId, codes }) => jsonResult((await gql.request(SEED_STARTER_LOCALES, { projectId, codes })).seedStarterLocales)
@@ -989,12 +1025,14 @@ function registerTranslationTools(server, gql) {
989
1025
  "export_locale",
990
1026
  {
991
1027
  title: "Export locale",
992
- description: "Exports a locale to a file string in the chosen format \u2014 the basis for writing translations back into local files. Set `onlyApproved` to exclude drafts.",
1028
+ description: 'Exports a locale to a file string in the chosen format \u2014 the basis for writing translations back into local files. Set `onlyApproved` to exclude drafts. For a multi-file project prefer `sourceFile` (the real path, e.g. "messages/en/home.json") to export exactly that file with file-local key names \u2014 reliable. `keyPrefix` is the legacy name-prefix heuristic.',
993
1029
  inputSchema: {
994
1030
  projectId: z13.string(),
995
1031
  localeId: z13.string(),
996
1032
  format: z13.enum(FORMATS2).default("JSON"),
997
- onlyApproved: z13.boolean().optional()
1033
+ onlyApproved: z13.boolean().optional(),
1034
+ sourceFile: z13.string().optional().describe('Real source file path to export, e.g. "messages/en/home.json" (reliable, preferred).'),
1035
+ keyPrefix: z13.string().optional().describe('Legacy namespace prefix, e.g. "home".')
998
1036
  },
999
1037
  annotations: { readOnlyHint: true, openWorldHint: true }
1000
1038
  },
@@ -1004,23 +1042,24 @@ function registerTranslationTools(server, gql) {
1004
1042
  "import_translations",
1005
1043
  {
1006
1044
  title: "Import translations",
1007
- description: "Imports ready-made translations for a locale (paste file content). `autoApprove` lands them as APPROVED; `conflict` is OVERWRITE | SKIP_EXISTING | KEEP_APPROVED; `dryRun` previews.",
1045
+ description: "Imports ready-made translations for a locale (paste file content). `autoApprove` lands them as APPROVED; `conflict` is OVERWRITE | SKIP_EXISTING | KEEP_APPROVED; `dryRun` previews. For a multi-file / namespaced project pass `keyPrefix` (the namespace) to match prefixed source keys.",
1008
1046
  inputSchema: {
1009
1047
  projectId: z13.string(),
1010
1048
  localeId: z13.string(),
1011
1049
  content: z13.string(),
1012
1050
  format: z13.enum(FORMATS2),
1051
+ keyPrefix: z13.string().optional().describe('Namespace prefix, e.g. "home" (per-file, multi-file projects).'),
1013
1052
  autoApprove: z13.boolean().default(false),
1014
1053
  conflict: z13.enum(["OVERWRITE", "SKIP_EXISTING", "KEEP_APPROVED"]).default("OVERWRITE"),
1015
1054
  dryRun: z13.boolean().default(false)
1016
1055
  },
1017
1056
  annotations: { idempotentHint: true, openWorldHint: true }
1018
1057
  },
1019
- async ({ projectId, localeId, content, format, autoApprove, conflict, dryRun }) => jsonResult(
1058
+ async ({ projectId, localeId, content, format, keyPrefix, autoApprove, conflict, dryRun }) => jsonResult(
1020
1059
  (await gql.request(IMPORT_TRANSLATIONS, {
1021
1060
  projectId,
1022
1061
  localeId,
1023
- input: { content, format, autoApprove, conflict, dryRun }
1062
+ input: { content, format, keyPrefix, autoApprove, conflict, dryRun }
1024
1063
  })).importTranslations
1025
1064
  )
1026
1065
  );
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  buildMcpServer,
4
4
  parseList
5
- } from "./chunk-QOVGANRN.js";
5
+ } from "./chunk-DZKWHV2O.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syntholoc/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Syntholoc MCP server — manage AI localization and translation from Claude Code.",
5
5
  "homepage": "https://syntholoc.com",
6
6
  "keywords": [