@atlaskit/ads-mcp 0.12.0 → 0.13.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/ads-mcp
2
2
 
3
+ ## 0.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 0.13.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`1fa31caed8915`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1fa31caed8915) -
14
+ Add mcp for i18n hardcoded string conversion
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies
19
+
3
20
  ## 0.12.0
4
21
 
5
22
  ### Minor Changes
package/dist/cjs/index.js CHANGED
@@ -22,6 +22,7 @@ var _getAllTokens = require("./tools/get-all-tokens");
22
22
  var _getComponents = require("./tools/get-components");
23
23
  var _getIcons = require("./tools/get-icons");
24
24
  var _getTokens = require("./tools/get-tokens");
25
+ var _i18nConversion = require("./tools/i18n-conversion");
25
26
  var _migrationGuides = require("./tools/migration-guides");
26
27
  var _plan = require("./tools/plan");
27
28
  var _suggestA11yFixes = require("./tools/suggest-a11y-fixes");
@@ -62,7 +63,7 @@ var generateLogger = function generateLogger(level) {
62
63
  };
63
64
  };
64
65
  var getToolRegistry = exports.getToolRegistry = function getToolRegistry() {
65
- var baseTools = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _analyzeA11y.listAnalyzeA11yTool.name, {
66
+ var baseTools = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _analyzeA11y.listAnalyzeA11yTool.name, {
66
67
  handler: _analyzeA11y.analyzeA11yTool,
67
68
  inputSchema: _analyzeA11y.analyzeA11yInputSchema,
68
69
  tool: _analyzeA11y.listAnalyzeA11yTool
@@ -90,6 +91,10 @@ var getToolRegistry = exports.getToolRegistry = function getToolRegistry() {
90
91
  handler: _migrationGuides.migrationGuidesTool,
91
92
  inputSchema: _migrationGuides.migrationGuidesInputSchema,
92
93
  tool: _migrationGuides.listMigrationGuidesTool
94
+ }), _i18nConversion.listI18nConversionTool.name, {
95
+ handler: _i18nConversion.i18nConversionTool,
96
+ inputSchema: _i18nConversion.i18nConversionInputSchema,
97
+ tool: _i18nConversion.listI18nConversionTool
93
98
  });
94
99
 
95
100
  // Conditionally add token and icon tools based on feature flag
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.i18nConversionGuide = void 0;
7
+ /**
8
+ * i18n Conversion Guide - Converting hardcoded strings to use formatMessage
9
+ */
10
+
11
+ var i18nConversionGuide = exports.i18nConversionGuide = {
12
+ id: 'hardcoded-string-to-formatmessage',
13
+ title: 'Hardcoded String to formatMessage Conversion Guide',
14
+ description: 'Comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next',
15
+ purpose: 'This guide instructs LLM agents to convert hardcoded strings to use formatMessage. The import source depends on the file path: use @atlassian/jira-intl for Jira files (path contains "jira"), and react-intl-next for any non-Jira files. The goal is to find all hardcoded strings in JSX and convert them to internationalized messages.',
16
+ scope: "**CRITICAL SCOPE**: This process should **ONLY** focus on converting hardcoded strings (literal strings in JSX, eslint-disable comments for no-literal-string-in-jsx, etc.) to use formatMessage. Do NOT modify pre-existing messages that were already in the codebase, even if they have poor descriptions, incorrect placeholder names, or other quality issues. Only convert NEW hardcoded strings.\n\n**PATH SCOPE LIMITATION**: If a specific file, package name, or path is provided, **ONLY** find and convert hardcoded strings within that specified path. Do NOT modify files outside the provided scope.\n\n**CRITICAL: FIX ALL VIOLATIONS IN FILE**: When a specific file is mentioned (even with line numbers like `@file.tsx:139-142`), you MUST find and fix **ALL** @atlassian/i18n/no-literal-string-in-jsx violations in that entire file, not just the specific lines mentioned. Scan the entire file for hardcoded strings and convert them all. The line numbers are just a reference point - the goal is to fix all i18n violations in the provided file.\n\n**CRITICAL: STRING FILTERING**: When finding eslint-disable comments, you MUST examine the actual string content. Only convert strings that contain **user-facing English text**. Many English strings are technical/non-user-facing and should NOT be converted (e.g., product names like \"Jira\", URLs like \"https://example.com\", technical IDs, symbols). Use the ESLint ignore patterns to identify which English strings to skip - strings matching ignore patterns should be LEFT AS-IS with their eslint-disable comments intact.",
17
+ implementationChecklist: ['Create message constants using defineMessage (singular) at the top of the file - use defineMessage for each individual message, NOT defineMessages', 'Import: Use "@atlassian/jira-intl" for Jira files (with eslint-disable) or "react-intl-next" for non-Jira files', '**CRITICAL: i18n ID Format**: For files in `/next/packages/`, i18n ids MUST start with the package name (with dashes). Format: `{package-name}.{component-or-feature}.{message-key}`. Example: For package `comment-extension-handlers`, use `comment-extension-handlers.legacy-content-modal.close-button`. For package `rovo-ai-search`, use `rovo-ai-search.view-profile-text` or `rovo-ai-search.knowledge-cards.copy-email-address`.', '**CRITICAL: ai-non-final Suffix**: ALL new message IDs MUST end with `.ai-non-final` suffix. This applies to ALL newly created messages, regardless of whether existing messages in the file have this suffix. Format: `{message-key}.ai-non-final`. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. This suffix indicates the message is AI-generated and may need review before finalization.', 'Add useIntl hook: const { formatMessage } = useIntl();', 'Replace hardcoded strings with formatMessage(messageKey)', 'Add descriptions (40+ chars, unique from defaultMessage) and descriptive placeholder names', 'Remove eslint-disable for no-literal-string-in-jsx ONLY after converting', '**VERIFICATION**: To verify all hardcoded strings are fixed, search for `@atlassian/i18n/no-literal-string-in-jsx` in the file. Do NOT run eslint - just search for the eslint-disable comments. If no matches are found, all hardcoded strings have been converted.'],
18
+ patterns: [{
19
+ title: 'Button Text and Action Labels',
20
+ description: 'Converting hardcoded button text with variables',
21
+ before: "<Button style={styles} onClick={onClearVersion}>\n Exit {description.join(' ')}\n</Button>",
22
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst exitButton = defineMessage({\n id: 'rovo-ai-search.exit-button.ai-non-final',\n defaultMessage: 'Exit {version}',\n description: 'The text is shown as a button when the user needs to exit the version view. The placeholder {version} will be substituted with the version name. Used in the version header to return to the main view.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Button style={styles} onClick={onClearVersion}>\n {formatMessage(exitButton, { version: description.join(' ') })}\n </Button>\n );\n}",
23
+ explanation: 'Convert button text with variables to use placeholders in the message. Use descriptive placeholder names that match the variable context.'
24
+ }, {
25
+ title: 'Loading States and Status Messages',
26
+ description: 'Converting hardcoded loading and status messages',
27
+ before: "return <Box>Loading...</Box>;",
28
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst loading = defineMessage({\n id: 'rovo-ai-search.loading.ai-non-final',\n defaultMessage: 'Loading...',\n description: 'The text is shown as a loading indicator when work item data is being fetched from the server. Appears in the work item tab to inform users that content is loading.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box>\n {formatMessage(loading)}\n </Box>\n );\n}",
29
+ explanation: 'Convert simple hardcoded status messages to use defineMessage (singular). Use defineMessage for each individual message, NOT defineMessages. Ensure descriptions explain when and where the message appears.'
30
+ }, {
31
+ title: 'Partial String Translation (Prefixes and Labels)',
32
+ description: 'Translating only part of a string while preserving UI structure',
33
+ before: "<Box xcss={styles}>\n <span>Status: {selectedOptions[0]?.label}</span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n</Box>",
34
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst statusLabel = defineMessage({\n id: 'filter.status-label.ai-non-final',\n defaultMessage: 'Status: {status}',\n description: 'The text is shown as a button label prefix for the status filter. The placeholder {status} will be substituted with the selected status label. Appears in the filter button to show the current filter state.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box xcss={styles}>\n <span>\n {formatMessage(statusLabel, {\n status: selectedOptions[0]?.label,\n })}\n </span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n </Box>\n );\n}",
35
+ explanation: 'When only part of a string needs translation (like a prefix "Status: " or label), create a message with a placeholder for the dynamic part. Preserve the existing UI structure (components, conditional rendering, etc.) and only translate the literal string portion.'
36
+ }, {
37
+ title: 'Dynamic Text Content',
38
+ description: 'Converting conditional hardcoded strings',
39
+ before: "<Text>{state.hasMore ? 'Show more' : 'Show less'}</Text>",
40
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst showMore = defineMessage({\n id: 'work-item.comments.show-more.ai-non-final',\n defaultMessage: 'Show more',\n description: 'The text is shown as a button when there are more items available to display. Used in the comments section footer to expand and show additional content.',\n});\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst showLess = defineMessage({\n id: 'work-item.comments.show-less.ai-non-final',\n defaultMessage: 'Show less',\n description: 'The text is shown as a button when the user wants to collapse the expanded view. Used in the comments section footer to hide additional content.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Text>\n {state.hasMore\n ? formatMessage(showMore)\n : formatMessage(showLess)}\n </Text>\n );\n}",
41
+ explanation: 'Create separate message constants for each conditional string. Use descriptive names that reflect the context.'
42
+ }, {
43
+ title: 'ICU Format for Numeric Values',
44
+ description: 'Converting numeric placeholders to ICU plural format',
45
+ before: "const moreMessage = `+ ${count} more`;",
46
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst moreProjectsMessage = defineMessage({\n id: 'app.more-projects.ai-non-final',\n defaultMessage: '{count, plural, one {+ # more} other {+ # more}}',\n description: 'The text is shown when there are additional projects available. The placeholder {count} will be substituted with the number of remaining projects. Uses ICU plural format for proper localization.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n const count = 5;\n\n return (\n <Text>\n {formatMessage(moreProjectsMessage, { count })}\n </Text>\n );\n}",
47
+ explanation: 'Use ICU plural format for numeric values that may need pluralization. The # symbol represents the numeric value in ICU format.'
48
+ }],
49
+ bestPractices: ['Only convert NEW hardcoded strings - do NOT modify pre-existing messages', '**CRITICAL: Use defineMessage (singular)** - Always use defineMessage for each individual message, NOT defineMessages (plural). Each message should be defined separately: `const messageKey = defineMessage({ ... })`', '**CRITICAL: Always use .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Format: `{message-key}.ai-non-final`. This indicates the message is AI-generated and may need review.', 'Use descriptive names (userNameLabel not label) and placeholders (userName not value)', 'Add descriptions (40+ chars, unique from defaultMessage)', 'Use ICU plural format for numeric values: {count, plural, one {...} other {...}}', 'Place message constants at top of file, use formatMessage consistently', 'Convert arrow functions to body form if needed for hooks'],
50
+ commonPitfalls: ['**CRITICAL**: Converting/removing eslint-disable for strings matching ignore patterns - technical/non-user-facing strings should remain hardcoded', '**CRITICAL: Invalid i18n ID format** - For files in `/next/packages/`, must follow `{package-name}.{component-or-feature}.{message-key}`. Common errors: (1) Not starting with package name (with dashes), (2) Missing component/feature segment, (3) Wrong message key format (must be kebab-case, not camelCase). Example correct: `comment-extension-handlers.legacy-content-modal.close-button`.', '**CRITICAL: Missing .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. Do NOT omit this suffix even when existing messages don\'t have it.', '**CRITICAL: Using defineMessages instead of defineMessage** - Always use defineMessage (singular) for individual messages, NOT defineMessages (plural). Each message should be defined separately using defineMessage.', 'Using wrong import path - "jira" path needs @atlassian/jira-intl (with eslint-disable), others use react-intl-next', 'Adding eslint-disable for defineMessage in non-Jira files or forgetting it in Jira files', 'Missing useIntl hook, generic placeholder names, or descriptions < 40 chars', 'Modifying pre-existing messages or working outside specified path scope'],
51
+ additionalResources: ['ESLint rule @atlassian/i18n/no-literal-string-in-jsx defines ignore patterns for technical strings (URLs, product names, symbols, etc.)']
52
+ };
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.listI18nConversionTool = exports.i18nConversionTool = exports.i18nConversionInputSchema = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
+ var _zod = require("zod");
11
+ var _helpers = require("../../helpers");
12
+ var _guide = require("./guide");
13
+ var i18nConversionInputSchema = exports.i18nConversionInputSchema = _zod.z.object({
14
+ guide: _zod.z.enum(['hardcoded-string-to-formatmessage']).describe('The i18n conversion guide to retrieve.')
15
+ });
16
+ var listI18nConversionTool = exports.listI18nConversionTool = {
17
+ name: 'ads_i18n_conversion_guide',
18
+ description: "Provides comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next.\n\n**TRIGGER**: Use this tool when you encounter:\n- \"fix hardcoded string\" / \"fix hardcoded strings\" / \"fix hardcoded string in [file]\"\n- \"convert hardcoded string\" / \"convert to i18n\" / \"convert to formatMessage\"\n- \"translate string\" / \"internationalize string\" / \"i18n this string\"\n- \"use formatMessage\" / \"use FormattedMessage\" / \"wrap in formatMessage\"\n- \"literal string\" / \"fix literal string\" / \"convert literal string\"\n- ESLint errors: \"Literal string in JSX content should be internationalized. Use FormattedMessage or intl.formatMessage()\"\n- ESLint errors: \"@atlassian/i18n/no-literal-string-in-jsx\"\n- Requests to convert hardcoded strings to i18n messages\n\nThis tool helps LLM agents systematically convert hardcoded strings while respecting scope limitations and following best practices for message constants, placeholders, and descriptions.",
19
+ annotations: {
20
+ title: 'i18n Conversion Guide',
21
+ readOnlyHint: true,
22
+ destructiveHint: false,
23
+ idempotentHint: true,
24
+ openWorldHint: false
25
+ },
26
+ inputSchema: (0, _helpers.zodToJsonSchema)(i18nConversionInputSchema)
27
+ };
28
+ var i18nConversionTool = exports.i18nConversionTool = /*#__PURE__*/function () {
29
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_params) {
30
+ return _regenerator.default.wrap(function _callee$(_context) {
31
+ while (1) switch (_context.prev = _context.next) {
32
+ case 0:
33
+ return _context.abrupt("return", {
34
+ content: [{
35
+ type: 'text',
36
+ text: JSON.stringify(_guide.i18nConversionGuide, null, 2)
37
+ }]
38
+ });
39
+ case 1:
40
+ case "end":
41
+ return _context.stop();
42
+ }
43
+ }, _callee);
44
+ }));
45
+ return function i18nConversionTool(_x) {
46
+ return _ref.apply(this, arguments);
47
+ };
48
+ }();
@@ -0,0 +1 @@
1
+ "use strict";
@@ -13,6 +13,7 @@ import { getAllTokensTool, listGetAllTokensTool } from './tools/get-all-tokens';
13
13
  import { getComponentsTool, listGetComponentsTool } from './tools/get-components';
14
14
  import { getIconsInputSchema, getIconsTool, listGetIconsTool } from './tools/get-icons';
15
15
  import { getTokensInputSchema, getTokensTool, listGetTokensTool } from './tools/get-tokens';
16
+ import { i18nConversionInputSchema, i18nConversionTool, listI18nConversionTool } from './tools/i18n-conversion';
16
17
  import { listMigrationGuidesTool, migrationGuidesInputSchema, migrationGuidesTool } from './tools/migration-guides';
17
18
  import { listPlanTool, planInputSchema, planTool } from './tools/plan';
18
19
  import { listSuggestA11yFixesTool, suggestA11yFixesInputSchema, suggestA11yFixesTool } from './tools/suggest-a11y-fixes';
@@ -98,6 +99,11 @@ export const getToolRegistry = () => {
98
99
  handler: migrationGuidesTool,
99
100
  inputSchema: migrationGuidesInputSchema,
100
101
  tool: listMigrationGuidesTool
102
+ },
103
+ [listI18nConversionTool.name]: {
104
+ handler: i18nConversionTool,
105
+ inputSchema: i18nConversionInputSchema,
106
+ tool: listI18nConversionTool
101
107
  }
102
108
  };
103
109
 
@@ -0,0 +1,170 @@
1
+ /**
2
+ * i18n Conversion Guide - Converting hardcoded strings to use formatMessage
3
+ */
4
+
5
+ export const i18nConversionGuide = {
6
+ id: 'hardcoded-string-to-formatmessage',
7
+ title: 'Hardcoded String to formatMessage Conversion Guide',
8
+ description: 'Comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next',
9
+ purpose: 'This guide instructs LLM agents to convert hardcoded strings to use formatMessage. The import source depends on the file path: use @atlassian/jira-intl for Jira files (path contains "jira"), and react-intl-next for any non-Jira files. The goal is to find all hardcoded strings in JSX and convert them to internationalized messages.',
10
+ scope: `**CRITICAL SCOPE**: This process should **ONLY** focus on converting hardcoded strings (literal strings in JSX, eslint-disable comments for no-literal-string-in-jsx, etc.) to use formatMessage. Do NOT modify pre-existing messages that were already in the codebase, even if they have poor descriptions, incorrect placeholder names, or other quality issues. Only convert NEW hardcoded strings.
11
+
12
+ **PATH SCOPE LIMITATION**: If a specific file, package name, or path is provided, **ONLY** find and convert hardcoded strings within that specified path. Do NOT modify files outside the provided scope.
13
+
14
+ **CRITICAL: FIX ALL VIOLATIONS IN FILE**: When a specific file is mentioned (even with line numbers like \`@file.tsx:139-142\`), you MUST find and fix **ALL** @atlassian/i18n/no-literal-string-in-jsx violations in that entire file, not just the specific lines mentioned. Scan the entire file for hardcoded strings and convert them all. The line numbers are just a reference point - the goal is to fix all i18n violations in the provided file.
15
+
16
+ **CRITICAL: STRING FILTERING**: When finding eslint-disable comments, you MUST examine the actual string content. Only convert strings that contain **user-facing English text**. Many English strings are technical/non-user-facing and should NOT be converted (e.g., product names like "Jira", URLs like "https://example.com", technical IDs, symbols). Use the ESLint ignore patterns to identify which English strings to skip - strings matching ignore patterns should be LEFT AS-IS with their eslint-disable comments intact.`,
17
+ implementationChecklist: ['Create message constants using defineMessage (singular) at the top of the file - use defineMessage for each individual message, NOT defineMessages', 'Import: Use "@atlassian/jira-intl" for Jira files (with eslint-disable) or "react-intl-next" for non-Jira files', '**CRITICAL: i18n ID Format**: For files in `/next/packages/`, i18n ids MUST start with the package name (with dashes). Format: `{package-name}.{component-or-feature}.{message-key}`. Example: For package `comment-extension-handlers`, use `comment-extension-handlers.legacy-content-modal.close-button`. For package `rovo-ai-search`, use `rovo-ai-search.view-profile-text` or `rovo-ai-search.knowledge-cards.copy-email-address`.', '**CRITICAL: ai-non-final Suffix**: ALL new message IDs MUST end with `.ai-non-final` suffix. This applies to ALL newly created messages, regardless of whether existing messages in the file have this suffix. Format: `{message-key}.ai-non-final`. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. This suffix indicates the message is AI-generated and may need review before finalization.', 'Add useIntl hook: const { formatMessage } = useIntl();', 'Replace hardcoded strings with formatMessage(messageKey)', 'Add descriptions (40+ chars, unique from defaultMessage) and descriptive placeholder names', 'Remove eslint-disable for no-literal-string-in-jsx ONLY after converting', '**VERIFICATION**: To verify all hardcoded strings are fixed, search for `@atlassian/i18n/no-literal-string-in-jsx` in the file. Do NOT run eslint - just search for the eslint-disable comments. If no matches are found, all hardcoded strings have been converted.'],
18
+ patterns: [{
19
+ title: 'Button Text and Action Labels',
20
+ description: 'Converting hardcoded button text with variables',
21
+ before: `<Button style={styles} onClick={onClearVersion}>
22
+ Exit {description.join(' ')}
23
+ </Button>`,
24
+ after: `// eslint-disable-next-line jira/deprecations/ban-identifiers
25
+ import { useIntl, defineMessage } from '@atlassian/jira-intl';
26
+
27
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
28
+ const exitButton = defineMessage({
29
+ id: 'rovo-ai-search.exit-button.ai-non-final',
30
+ defaultMessage: 'Exit {version}',
31
+ description: 'The text is shown as a button when the user needs to exit the version view. The placeholder {version} will be substituted with the version name. Used in the version header to return to the main view.',
32
+ });
33
+
34
+ export function MyComponent() {
35
+ const { formatMessage } = useIntl();
36
+
37
+ return (
38
+ <Button style={styles} onClick={onClearVersion}>
39
+ {formatMessage(exitButton, { version: description.join(' ') })}
40
+ </Button>
41
+ );
42
+ }`,
43
+ explanation: 'Convert button text with variables to use placeholders in the message. Use descriptive placeholder names that match the variable context.'
44
+ }, {
45
+ title: 'Loading States and Status Messages',
46
+ description: 'Converting hardcoded loading and status messages',
47
+ before: `return <Box>Loading...</Box>;`,
48
+ after: `// eslint-disable-next-line jira/deprecations/ban-identifiers
49
+ import { useIntl, defineMessage } from '@atlassian/jira-intl';
50
+
51
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
52
+ const loading = defineMessage({
53
+ id: 'rovo-ai-search.loading.ai-non-final',
54
+ defaultMessage: 'Loading...',
55
+ description: 'The text is shown as a loading indicator when work item data is being fetched from the server. Appears in the work item tab to inform users that content is loading.',
56
+ });
57
+
58
+ export function MyComponent() {
59
+ const { formatMessage } = useIntl();
60
+
61
+ return (
62
+ <Box>
63
+ {formatMessage(loading)}
64
+ </Box>
65
+ );
66
+ }`,
67
+ explanation: 'Convert simple hardcoded status messages to use defineMessage (singular). Use defineMessage for each individual message, NOT defineMessages. Ensure descriptions explain when and where the message appears.'
68
+ }, {
69
+ title: 'Partial String Translation (Prefixes and Labels)',
70
+ description: 'Translating only part of a string while preserving UI structure',
71
+ before: `<Box xcss={styles}>
72
+ <span>Status: {selectedOptions[0]?.label}</span>
73
+ {selectedOptions.length > 1 && (
74
+ <Badge appearance="primary" max={99}>
75
+ +{selectedOptions.length - 1}
76
+ </Badge>
77
+ )}
78
+ </Box>`,
79
+ after: `// eslint-disable-next-line jira/deprecations/ban-identifiers
80
+ import { useIntl, defineMessage } from '@atlassian/jira-intl';
81
+
82
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
83
+ const statusLabel = defineMessage({
84
+ id: 'filter.status-label.ai-non-final',
85
+ defaultMessage: 'Status: {status}',
86
+ description: 'The text is shown as a button label prefix for the status filter. The placeholder {status} will be substituted with the selected status label. Appears in the filter button to show the current filter state.',
87
+ });
88
+
89
+ export function MyComponent() {
90
+ const { formatMessage } = useIntl();
91
+
92
+ return (
93
+ <Box xcss={styles}>
94
+ <span>
95
+ {formatMessage(statusLabel, {
96
+ status: selectedOptions[0]?.label,
97
+ })}
98
+ </span>
99
+ {selectedOptions.length > 1 && (
100
+ <Badge appearance="primary" max={99}>
101
+ +{selectedOptions.length - 1}
102
+ </Badge>
103
+ )}
104
+ </Box>
105
+ );
106
+ }`,
107
+ explanation: 'When only part of a string needs translation (like a prefix "Status: " or label), create a message with a placeholder for the dynamic part. Preserve the existing UI structure (components, conditional rendering, etc.) and only translate the literal string portion.'
108
+ }, {
109
+ title: 'Dynamic Text Content',
110
+ description: 'Converting conditional hardcoded strings',
111
+ before: `<Text>{state.hasMore ? 'Show more' : 'Show less'}</Text>`,
112
+ after: `// eslint-disable-next-line jira/deprecations/ban-identifiers
113
+ import { useIntl, defineMessage } from '@atlassian/jira-intl';
114
+
115
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
116
+ const showMore = defineMessage({
117
+ id: 'work-item.comments.show-more.ai-non-final',
118
+ defaultMessage: 'Show more',
119
+ description: 'The text is shown as a button when there are more items available to display. Used in the comments section footer to expand and show additional content.',
120
+ });
121
+
122
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
123
+ const showLess = defineMessage({
124
+ id: 'work-item.comments.show-less.ai-non-final',
125
+ defaultMessage: 'Show less',
126
+ description: 'The text is shown as a button when the user wants to collapse the expanded view. Used in the comments section footer to hide additional content.',
127
+ });
128
+
129
+ export function MyComponent() {
130
+ const { formatMessage } = useIntl();
131
+
132
+ return (
133
+ <Text>
134
+ {state.hasMore
135
+ ? formatMessage(showMore)
136
+ : formatMessage(showLess)}
137
+ </Text>
138
+ );
139
+ }`,
140
+ explanation: 'Create separate message constants for each conditional string. Use descriptive names that reflect the context.'
141
+ }, {
142
+ title: 'ICU Format for Numeric Values',
143
+ description: 'Converting numeric placeholders to ICU plural format',
144
+ before: `const moreMessage = \`+ \${count} more\`;`,
145
+ after: `// eslint-disable-next-line jira/deprecations/ban-identifiers
146
+ import { useIntl, defineMessage } from '@atlassian/jira-intl';
147
+
148
+ // eslint-disable-next-line jira/deprecations/ban-identifiers
149
+ const moreProjectsMessage = defineMessage({
150
+ id: 'app.more-projects.ai-non-final',
151
+ defaultMessage: '{count, plural, one {+ # more} other {+ # more}}',
152
+ description: 'The text is shown when there are additional projects available. The placeholder {count} will be substituted with the number of remaining projects. Uses ICU plural format for proper localization.',
153
+ });
154
+
155
+ export function MyComponent() {
156
+ const { formatMessage } = useIntl();
157
+ const count = 5;
158
+
159
+ return (
160
+ <Text>
161
+ {formatMessage(moreProjectsMessage, { count })}
162
+ </Text>
163
+ );
164
+ }`,
165
+ explanation: 'Use ICU plural format for numeric values that may need pluralization. The # symbol represents the numeric value in ICU format.'
166
+ }],
167
+ bestPractices: ['Only convert NEW hardcoded strings - do NOT modify pre-existing messages', '**CRITICAL: Use defineMessage (singular)** - Always use defineMessage for each individual message, NOT defineMessages (plural). Each message should be defined separately: `const messageKey = defineMessage({ ... })`', '**CRITICAL: Always use .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Format: `{message-key}.ai-non-final`. This indicates the message is AI-generated and may need review.', 'Use descriptive names (userNameLabel not label) and placeholders (userName not value)', 'Add descriptions (40+ chars, unique from defaultMessage)', 'Use ICU plural format for numeric values: {count, plural, one {...} other {...}}', 'Place message constants at top of file, use formatMessage consistently', 'Convert arrow functions to body form if needed for hooks'],
168
+ commonPitfalls: ['**CRITICAL**: Converting/removing eslint-disable for strings matching ignore patterns - technical/non-user-facing strings should remain hardcoded', '**CRITICAL: Invalid i18n ID format** - For files in `/next/packages/`, must follow `{package-name}.{component-or-feature}.{message-key}`. Common errors: (1) Not starting with package name (with dashes), (2) Missing component/feature segment, (3) Wrong message key format (must be kebab-case, not camelCase). Example correct: `comment-extension-handlers.legacy-content-modal.close-button`.', '**CRITICAL: Missing .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. Do NOT omit this suffix even when existing messages don\'t have it.', '**CRITICAL: Using defineMessages instead of defineMessage** - Always use defineMessage (singular) for individual messages, NOT defineMessages (plural). Each message should be defined separately using defineMessage.', 'Using wrong import path - "jira" path needs @atlassian/jira-intl (with eslint-disable), others use react-intl-next', 'Adding eslint-disable for defineMessage in non-Jira files or forgetting it in Jira files', 'Missing useIntl hook, generic placeholder names, or descriptions < 40 chars', 'Modifying pre-existing messages or working outside specified path scope'],
169
+ additionalResources: ['ESLint rule @atlassian/i18n/no-literal-string-in-jsx defines ignore patterns for technical strings (URLs, product names, symbols, etc.)']
170
+ };
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from '../../helpers';
3
+ import { i18nConversionGuide } from './guide';
4
+ export const i18nConversionInputSchema = z.object({
5
+ guide: z.enum(['hardcoded-string-to-formatmessage']).describe('The i18n conversion guide to retrieve.')
6
+ });
7
+ export const listI18nConversionTool = {
8
+ name: 'ads_i18n_conversion_guide',
9
+ description: `Provides comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next.
10
+
11
+ **TRIGGER**: Use this tool when you encounter:
12
+ - "fix hardcoded string" / "fix hardcoded strings" / "fix hardcoded string in [file]"
13
+ - "convert hardcoded string" / "convert to i18n" / "convert to formatMessage"
14
+ - "translate string" / "internationalize string" / "i18n this string"
15
+ - "use formatMessage" / "use FormattedMessage" / "wrap in formatMessage"
16
+ - "literal string" / "fix literal string" / "convert literal string"
17
+ - ESLint errors: "Literal string in JSX content should be internationalized. Use FormattedMessage or intl.formatMessage()"
18
+ - ESLint errors: "@atlassian/i18n/no-literal-string-in-jsx"
19
+ - Requests to convert hardcoded strings to i18n messages
20
+
21
+ This tool helps LLM agents systematically convert hardcoded strings while respecting scope limitations and following best practices for message constants, placeholders, and descriptions.`,
22
+ annotations: {
23
+ title: 'i18n Conversion Guide',
24
+ readOnlyHint: true,
25
+ destructiveHint: false,
26
+ idempotentHint: true,
27
+ openWorldHint: false
28
+ },
29
+ inputSchema: zodToJsonSchema(i18nConversionInputSchema)
30
+ };
31
+ export const i18nConversionTool = async _params => {
32
+ return {
33
+ content: [{
34
+ type: 'text',
35
+ text: JSON.stringify(i18nConversionGuide, null, 2)
36
+ }]
37
+ };
38
+ };
File without changes
package/dist/esm/index.js CHANGED
@@ -16,6 +16,7 @@ import { getAllTokensTool, listGetAllTokensTool } from './tools/get-all-tokens';
16
16
  import { getComponentsTool, listGetComponentsTool } from './tools/get-components';
17
17
  import { getIconsInputSchema, getIconsTool, listGetIconsTool } from './tools/get-icons';
18
18
  import { getTokensInputSchema, getTokensTool, listGetTokensTool } from './tools/get-tokens';
19
+ import { i18nConversionInputSchema, i18nConversionTool, listI18nConversionTool } from './tools/i18n-conversion';
19
20
  import { listMigrationGuidesTool, migrationGuidesInputSchema, migrationGuidesTool } from './tools/migration-guides';
20
21
  import { listPlanTool, planInputSchema, planTool } from './tools/plan';
21
22
  import { listSuggestA11yFixesTool, suggestA11yFixesInputSchema, suggestA11yFixesTool } from './tools/suggest-a11y-fixes';
@@ -55,7 +56,7 @@ var generateLogger = function generateLogger(level) {
55
56
  };
56
57
  };
57
58
  export var getToolRegistry = function getToolRegistry() {
58
- var baseTools = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, listAnalyzeA11yTool.name, {
59
+ var baseTools = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, listAnalyzeA11yTool.name, {
59
60
  handler: analyzeA11yTool,
60
61
  inputSchema: analyzeA11yInputSchema,
61
62
  tool: listAnalyzeA11yTool
@@ -83,6 +84,10 @@ export var getToolRegistry = function getToolRegistry() {
83
84
  handler: migrationGuidesTool,
84
85
  inputSchema: migrationGuidesInputSchema,
85
86
  tool: listMigrationGuidesTool
87
+ }), listI18nConversionTool.name, {
88
+ handler: i18nConversionTool,
89
+ inputSchema: i18nConversionInputSchema,
90
+ tool: listI18nConversionTool
86
91
  });
87
92
 
88
93
  // Conditionally add token and icon tools based on feature flag
@@ -0,0 +1,46 @@
1
+ /**
2
+ * i18n Conversion Guide - Converting hardcoded strings to use formatMessage
3
+ */
4
+
5
+ export var i18nConversionGuide = {
6
+ id: 'hardcoded-string-to-formatmessage',
7
+ title: 'Hardcoded String to formatMessage Conversion Guide',
8
+ description: 'Comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next',
9
+ purpose: 'This guide instructs LLM agents to convert hardcoded strings to use formatMessage. The import source depends on the file path: use @atlassian/jira-intl for Jira files (path contains "jira"), and react-intl-next for any non-Jira files. The goal is to find all hardcoded strings in JSX and convert them to internationalized messages.',
10
+ scope: "**CRITICAL SCOPE**: This process should **ONLY** focus on converting hardcoded strings (literal strings in JSX, eslint-disable comments for no-literal-string-in-jsx, etc.) to use formatMessage. Do NOT modify pre-existing messages that were already in the codebase, even if they have poor descriptions, incorrect placeholder names, or other quality issues. Only convert NEW hardcoded strings.\n\n**PATH SCOPE LIMITATION**: If a specific file, package name, or path is provided, **ONLY** find and convert hardcoded strings within that specified path. Do NOT modify files outside the provided scope.\n\n**CRITICAL: FIX ALL VIOLATIONS IN FILE**: When a specific file is mentioned (even with line numbers like `@file.tsx:139-142`), you MUST find and fix **ALL** @atlassian/i18n/no-literal-string-in-jsx violations in that entire file, not just the specific lines mentioned. Scan the entire file for hardcoded strings and convert them all. The line numbers are just a reference point - the goal is to fix all i18n violations in the provided file.\n\n**CRITICAL: STRING FILTERING**: When finding eslint-disable comments, you MUST examine the actual string content. Only convert strings that contain **user-facing English text**. Many English strings are technical/non-user-facing and should NOT be converted (e.g., product names like \"Jira\", URLs like \"https://example.com\", technical IDs, symbols). Use the ESLint ignore patterns to identify which English strings to skip - strings matching ignore patterns should be LEFT AS-IS with their eslint-disable comments intact.",
11
+ implementationChecklist: ['Create message constants using defineMessage (singular) at the top of the file - use defineMessage for each individual message, NOT defineMessages', 'Import: Use "@atlassian/jira-intl" for Jira files (with eslint-disable) or "react-intl-next" for non-Jira files', '**CRITICAL: i18n ID Format**: For files in `/next/packages/`, i18n ids MUST start with the package name (with dashes). Format: `{package-name}.{component-or-feature}.{message-key}`. Example: For package `comment-extension-handlers`, use `comment-extension-handlers.legacy-content-modal.close-button`. For package `rovo-ai-search`, use `rovo-ai-search.view-profile-text` or `rovo-ai-search.knowledge-cards.copy-email-address`.', '**CRITICAL: ai-non-final Suffix**: ALL new message IDs MUST end with `.ai-non-final` suffix. This applies to ALL newly created messages, regardless of whether existing messages in the file have this suffix. Format: `{message-key}.ai-non-final`. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. This suffix indicates the message is AI-generated and may need review before finalization.', 'Add useIntl hook: const { formatMessage } = useIntl();', 'Replace hardcoded strings with formatMessage(messageKey)', 'Add descriptions (40+ chars, unique from defaultMessage) and descriptive placeholder names', 'Remove eslint-disable for no-literal-string-in-jsx ONLY after converting', '**VERIFICATION**: To verify all hardcoded strings are fixed, search for `@atlassian/i18n/no-literal-string-in-jsx` in the file. Do NOT run eslint - just search for the eslint-disable comments. If no matches are found, all hardcoded strings have been converted.'],
12
+ patterns: [{
13
+ title: 'Button Text and Action Labels',
14
+ description: 'Converting hardcoded button text with variables',
15
+ before: "<Button style={styles} onClick={onClearVersion}>\n Exit {description.join(' ')}\n</Button>",
16
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst exitButton = defineMessage({\n id: 'rovo-ai-search.exit-button.ai-non-final',\n defaultMessage: 'Exit {version}',\n description: 'The text is shown as a button when the user needs to exit the version view. The placeholder {version} will be substituted with the version name. Used in the version header to return to the main view.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Button style={styles} onClick={onClearVersion}>\n {formatMessage(exitButton, { version: description.join(' ') })}\n </Button>\n );\n}",
17
+ explanation: 'Convert button text with variables to use placeholders in the message. Use descriptive placeholder names that match the variable context.'
18
+ }, {
19
+ title: 'Loading States and Status Messages',
20
+ description: 'Converting hardcoded loading and status messages',
21
+ before: "return <Box>Loading...</Box>;",
22
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst loading = defineMessage({\n id: 'rovo-ai-search.loading.ai-non-final',\n defaultMessage: 'Loading...',\n description: 'The text is shown as a loading indicator when work item data is being fetched from the server. Appears in the work item tab to inform users that content is loading.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box>\n {formatMessage(loading)}\n </Box>\n );\n}",
23
+ explanation: 'Convert simple hardcoded status messages to use defineMessage (singular). Use defineMessage for each individual message, NOT defineMessages. Ensure descriptions explain when and where the message appears.'
24
+ }, {
25
+ title: 'Partial String Translation (Prefixes and Labels)',
26
+ description: 'Translating only part of a string while preserving UI structure',
27
+ before: "<Box xcss={styles}>\n <span>Status: {selectedOptions[0]?.label}</span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n</Box>",
28
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst statusLabel = defineMessage({\n id: 'filter.status-label.ai-non-final',\n defaultMessage: 'Status: {status}',\n description: 'The text is shown as a button label prefix for the status filter. The placeholder {status} will be substituted with the selected status label. Appears in the filter button to show the current filter state.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box xcss={styles}>\n <span>\n {formatMessage(statusLabel, {\n status: selectedOptions[0]?.label,\n })}\n </span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n </Box>\n );\n}",
29
+ explanation: 'When only part of a string needs translation (like a prefix "Status: " or label), create a message with a placeholder for the dynamic part. Preserve the existing UI structure (components, conditional rendering, etc.) and only translate the literal string portion.'
30
+ }, {
31
+ title: 'Dynamic Text Content',
32
+ description: 'Converting conditional hardcoded strings',
33
+ before: "<Text>{state.hasMore ? 'Show more' : 'Show less'}</Text>",
34
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst showMore = defineMessage({\n id: 'work-item.comments.show-more.ai-non-final',\n defaultMessage: 'Show more',\n description: 'The text is shown as a button when there are more items available to display. Used in the comments section footer to expand and show additional content.',\n});\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst showLess = defineMessage({\n id: 'work-item.comments.show-less.ai-non-final',\n defaultMessage: 'Show less',\n description: 'The text is shown as a button when the user wants to collapse the expanded view. Used in the comments section footer to hide additional content.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Text>\n {state.hasMore\n ? formatMessage(showMore)\n : formatMessage(showLess)}\n </Text>\n );\n}",
35
+ explanation: 'Create separate message constants for each conditional string. Use descriptive names that reflect the context.'
36
+ }, {
37
+ title: 'ICU Format for Numeric Values',
38
+ description: 'Converting numeric placeholders to ICU plural format',
39
+ before: "const moreMessage = `+ ${count} more`;",
40
+ after: "// eslint-disable-next-line jira/deprecations/ban-identifiers\nimport { useIntl, defineMessage } from '@atlassian/jira-intl';\n\n// eslint-disable-next-line jira/deprecations/ban-identifiers\nconst moreProjectsMessage = defineMessage({\n id: 'app.more-projects.ai-non-final',\n defaultMessage: '{count, plural, one {+ # more} other {+ # more}}',\n description: 'The text is shown when there are additional projects available. The placeholder {count} will be substituted with the number of remaining projects. Uses ICU plural format for proper localization.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n const count = 5;\n\n return (\n <Text>\n {formatMessage(moreProjectsMessage, { count })}\n </Text>\n );\n}",
41
+ explanation: 'Use ICU plural format for numeric values that may need pluralization. The # symbol represents the numeric value in ICU format.'
42
+ }],
43
+ bestPractices: ['Only convert NEW hardcoded strings - do NOT modify pre-existing messages', '**CRITICAL: Use defineMessage (singular)** - Always use defineMessage for each individual message, NOT defineMessages (plural). Each message should be defined separately: `const messageKey = defineMessage({ ... })`', '**CRITICAL: Always use .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Format: `{message-key}.ai-non-final`. This indicates the message is AI-generated and may need review.', 'Use descriptive names (userNameLabel not label) and placeholders (userName not value)', 'Add descriptions (40+ chars, unique from defaultMessage)', 'Use ICU plural format for numeric values: {count, plural, one {...} other {...}}', 'Place message constants at top of file, use formatMessage consistently', 'Convert arrow functions to body form if needed for hooks'],
44
+ commonPitfalls: ['**CRITICAL**: Converting/removing eslint-disable for strings matching ignore patterns - technical/non-user-facing strings should remain hardcoded', '**CRITICAL: Invalid i18n ID format** - For files in `/next/packages/`, must follow `{package-name}.{component-or-feature}.{message-key}`. Common errors: (1) Not starting with package name (with dashes), (2) Missing component/feature segment, (3) Wrong message key format (must be kebab-case, not camelCase). Example correct: `comment-extension-handlers.legacy-content-modal.close-button`.', '**CRITICAL: Missing .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. Do NOT omit this suffix even when existing messages don\'t have it.', '**CRITICAL: Using defineMessages instead of defineMessage** - Always use defineMessage (singular) for individual messages, NOT defineMessages (plural). Each message should be defined separately using defineMessage.', 'Using wrong import path - "jira" path needs @atlassian/jira-intl (with eslint-disable), others use react-intl-next', 'Adding eslint-disable for defineMessage in non-Jira files or forgetting it in Jira files', 'Missing useIntl hook, generic placeholder names, or descriptions < 40 chars', 'Modifying pre-existing messages or working outside specified path scope'],
45
+ additionalResources: ['ESLint rule @atlassian/i18n/no-literal-string-in-jsx defines ignore patterns for technical strings (URLs, product names, symbols, etc.)']
46
+ };
@@ -0,0 +1,41 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { z } from 'zod';
4
+ import { zodToJsonSchema } from '../../helpers';
5
+ import { i18nConversionGuide } from './guide';
6
+ export var i18nConversionInputSchema = z.object({
7
+ guide: z.enum(['hardcoded-string-to-formatmessage']).describe('The i18n conversion guide to retrieve.')
8
+ });
9
+ export var listI18nConversionTool = {
10
+ name: 'ads_i18n_conversion_guide',
11
+ description: "Provides comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next.\n\n**TRIGGER**: Use this tool when you encounter:\n- \"fix hardcoded string\" / \"fix hardcoded strings\" / \"fix hardcoded string in [file]\"\n- \"convert hardcoded string\" / \"convert to i18n\" / \"convert to formatMessage\"\n- \"translate string\" / \"internationalize string\" / \"i18n this string\"\n- \"use formatMessage\" / \"use FormattedMessage\" / \"wrap in formatMessage\"\n- \"literal string\" / \"fix literal string\" / \"convert literal string\"\n- ESLint errors: \"Literal string in JSX content should be internationalized. Use FormattedMessage or intl.formatMessage()\"\n- ESLint errors: \"@atlassian/i18n/no-literal-string-in-jsx\"\n- Requests to convert hardcoded strings to i18n messages\n\nThis tool helps LLM agents systematically convert hardcoded strings while respecting scope limitations and following best practices for message constants, placeholders, and descriptions.",
12
+ annotations: {
13
+ title: 'i18n Conversion Guide',
14
+ readOnlyHint: true,
15
+ destructiveHint: false,
16
+ idempotentHint: true,
17
+ openWorldHint: false
18
+ },
19
+ inputSchema: zodToJsonSchema(i18nConversionInputSchema)
20
+ };
21
+ export var i18nConversionTool = /*#__PURE__*/function () {
22
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_params) {
23
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
24
+ while (1) switch (_context.prev = _context.next) {
25
+ case 0:
26
+ return _context.abrupt("return", {
27
+ content: [{
28
+ type: 'text',
29
+ text: JSON.stringify(i18nConversionGuide, null, 2)
30
+ }]
31
+ });
32
+ case 1:
33
+ case "end":
34
+ return _context.stop();
35
+ }
36
+ }, _callee);
37
+ }));
38
+ return function i18nConversionTool(_x) {
39
+ return _ref.apply(this, arguments);
40
+ };
41
+ }();
File without changes
@@ -0,0 +1,5 @@
1
+ /**
2
+ * i18n Conversion Guide - Converting hardcoded strings to use formatMessage
3
+ */
4
+ import type { ConversionGuide } from './types';
5
+ export declare const i18nConversionGuide: ConversionGuide;
@@ -0,0 +1,16 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types';
2
+ import { z } from 'zod';
3
+ export declare const i18nConversionInputSchema: z.ZodObject<{
4
+ guide: z.ZodEnum<["hardcoded-string-to-formatmessage"]>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ guide: "hardcoded-string-to-formatmessage";
7
+ }, {
8
+ guide: "hardcoded-string-to-formatmessage";
9
+ }>;
10
+ export declare const listI18nConversionTool: Tool;
11
+ export declare const i18nConversionTool: (_params: z.infer<typeof i18nConversionInputSchema>) => Promise<{
12
+ content: {
13
+ type: string;
14
+ text: string;
15
+ }[];
16
+ }>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Types for the i18n conversion tool
3
+ */
4
+ export interface ConversionPattern {
5
+ title: string;
6
+ description: string;
7
+ before: string;
8
+ after: string;
9
+ explanation: string;
10
+ }
11
+ export interface ConversionGuide {
12
+ id: string;
13
+ title: string;
14
+ description: string;
15
+ purpose: string;
16
+ scope: string;
17
+ implementationChecklist: string[];
18
+ patterns: ConversionPattern[];
19
+ bestPractices: string[];
20
+ commonPitfalls: string[];
21
+ additionalResources: string | string[];
22
+ }
@@ -7,15 +7,15 @@ export declare const planInputSchema: z.ZodObject<{
7
7
  limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
8
8
  exactName: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- components: string[];
11
10
  tokens: string[];
12
11
  icons: string[];
12
+ components: string[];
13
13
  limit?: number | undefined;
14
14
  exactName?: boolean | undefined;
15
15
  }, {
16
- components: string[];
17
16
  tokens: string[];
18
17
  icons: string[];
18
+ components: string[];
19
19
  limit?: number | undefined;
20
20
  exactName?: boolean | undefined;
21
21
  }>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * i18n Conversion Guide - Converting hardcoded strings to use formatMessage
3
+ */
4
+ import type { ConversionGuide } from './types';
5
+ export declare const i18nConversionGuide: ConversionGuide;
@@ -0,0 +1,18 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types';
2
+ import { z } from 'zod';
3
+ export declare const i18nConversionInputSchema: z.ZodObject<{
4
+ guide: z.ZodEnum<[
5
+ "hardcoded-string-to-formatmessage"
6
+ ]>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ guide: "hardcoded-string-to-formatmessage";
9
+ }, {
10
+ guide: "hardcoded-string-to-formatmessage";
11
+ }>;
12
+ export declare const listI18nConversionTool: Tool;
13
+ export declare const i18nConversionTool: (_params: z.infer<typeof i18nConversionInputSchema>) => Promise<{
14
+ content: {
15
+ type: string;
16
+ text: string;
17
+ }[];
18
+ }>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Types for the i18n conversion tool
3
+ */
4
+ export interface ConversionPattern {
5
+ title: string;
6
+ description: string;
7
+ before: string;
8
+ after: string;
9
+ explanation: string;
10
+ }
11
+ export interface ConversionGuide {
12
+ id: string;
13
+ title: string;
14
+ description: string;
15
+ purpose: string;
16
+ scope: string;
17
+ implementationChecklist: string[];
18
+ patterns: ConversionPattern[];
19
+ bestPractices: string[];
20
+ commonPitfalls: string[];
21
+ additionalResources: string | string[];
22
+ }
@@ -7,15 +7,15 @@ export declare const planInputSchema: z.ZodObject<{
7
7
  limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
8
8
  exactName: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- components: string[];
11
10
  tokens: string[];
12
11
  icons: string[];
12
+ components: string[];
13
13
  limit?: number | undefined;
14
14
  exactName?: boolean | undefined;
15
15
  }, {
16
- components: string[];
17
16
  tokens: string[];
18
17
  icons: string[];
18
+ components: string[];
19
19
  limit?: number | undefined;
20
20
  exactName?: boolean | undefined;
21
21
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/ads-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "The official Atlassian Design System MCP server to develop apps and user interfaces matching the Atlassian style.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -34,9 +34,9 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@atlaskit/icon": "^29.2.0",
37
+ "@atlaskit/icon": "^29.3.0",
38
38
  "@atlaskit/platform-feature-flags": "^1.1.0",
39
- "@atlaskit/tokens": "^8.6.0",
39
+ "@atlaskit/tokens": "^9.0.0",
40
40
  "@axe-core/playwright": "^4.8.0",
41
41
  "@axe-core/puppeteer": "^4.7.3",
42
42
  "@babel/runtime": "^7.0.0",