@intlayer/svelte-compiler 8.9.2 → 8.9.4-canary.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractSvelteFieldUsage.cjs","names":[],"sources":["../../src/extractSvelteFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Svelte SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Svelte's `useIntlayer` returns a Svelte store. Inside both the `<script>`\n * block and the `<template>`, stores are consumed via the auto-subscription\n * prefix `$` — so `const bm = useIntlayer('benchmark')` is accessed as\n * `$bm.fieldName`.\n *\n * Because Svelte's `$store` syntax is preprocessed before standard JS tools\n * see the file, static Babel-scope analysis cannot link `$bm` back to `bm`.\n * This module fills that gap by running a targeted regex over the raw source.\n */\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`bm` in `const bm = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Analyzes a Svelte SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * Matches the Svelte reactive store access pattern `$varName.fieldName`\n * throughout the entire file (both `<script>` and markup regions).\n *\n * @param code - Full `.svelte` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractSvelteIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // Svelte store auto-subscription: $varName.fieldName\n // The `$` must be preceded by a word boundary or the start of the\n // expression (not an identifier character) to avoid false positives such\n // as matching `$$bm.field` or `_$bm.field`.\n const storeRe = new RegExp(`(?<![\\\\w$])\\\\$${esc}\\\\.(\\\\w+)`, 'g');\n\n storeRe.lastIndex = 0;\n for (let m = storeRe.exec(code); m !== null; m = storeRe.exec(code)) {\n fields.add(m[1]);\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,OAAO;;;;;;;;;;;;;;AAuB5C,MAAa,mCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,KAA0B;
|
|
1
|
+
{"version":3,"file":"extractSvelteFieldUsage.cjs","names":[],"sources":["../../src/extractSvelteFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Svelte SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Svelte's `useIntlayer` returns a Svelte store. Inside both the `<script>`\n * block and the `<template>`, stores are consumed via the auto-subscription\n * prefix `$` — so `const bm = useIntlayer('benchmark')` is accessed as\n * `$bm.fieldName`.\n *\n * Because Svelte's `$store` syntax is preprocessed before standard JS tools\n * see the file, static Babel-scope analysis cannot link `$bm` back to `bm`.\n * This module fills that gap by running a targeted regex over the raw source.\n */\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`bm` in `const bm = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Analyzes a Svelte SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * Matches the Svelte reactive store access pattern `$varName.fieldName`\n * throughout the entire file (both `<script>` and markup regions).\n *\n * @param code - Full `.svelte` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractSvelteIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // Svelte store auto-subscription: $varName.fieldName\n // The `$` must be preceded by a word boundary or the start of the\n // expression (not an identifier character) to avoid false positives such\n // as matching `$$bm.field` or `_$bm.field`.\n const storeRe = new RegExp(`(?<![\\\\w$])\\\\$${esc}\\\\.(\\\\w+)`, 'g');\n\n storeRe.lastIndex = 0;\n for (let m = storeRe.exec(code); m !== null; m = storeRe.exec(code)) {\n fields.add(m[1]);\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,OAAO;;;;;;;;;;;;;;AAuB5C,MAAa,mCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,KAA0B;CAE7C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,KAAa;EAChC,MAAM,MAAM,aAAa,aAAa;EAMtC,MAAM,UAAU,IAAI,OAAO,iBAAiB,IAAI,YAAY,IAAI;EAEhE,QAAQ,YAAY;EACpB,KAAK,IAAI,IAAI,QAAQ,KAAK,KAAK,EAAE,MAAM,MAAM,IAAI,QAAQ,KAAK,KAAK,EACjE,OAAO,IAAI,EAAE,GAAG;EAGlB,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,OAAO;;CAIrC,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte-intlayer-extract.cjs","names":["t","DEFAULT_LOCALE","MagicString"],"sources":["../../src/svelte-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport MagicString from 'magic-string';\nimport { parse } from 'svelte/compiler';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether a script block already contains a `useIntlayer` /\n * `getIntlayer` call and whether its result is destructured.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (p: any): p is typeof t.objectProperty =>\n t.isObjectProperty(p) && t.isIdentifier(p.key)\n )\n .map((p: any) => (p.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\n/* ────────────────────────────────────────── helpers ─────────────────────── */\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\n/* ────────────────────────────────────────── plugin ──────────────────────── */\n\nexport const intlayerSvelteExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'svelte-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.svelte')) return null;\n\n const magic = new MagicString(code);\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Extract and walk Script using Babel\n const scriptRegex = /<script[^>]*>([\\s\\S]*?)<\\/script>/;\n const scriptMatch = scriptRegex.exec(code);\n let hasScriptExtraction = false;\n const scriptContent = scriptMatch ? scriptMatch[1] : '';\n\n // Detect existing call BEFORE walking the template so the access pattern\n // (bare key vs. $content.key) can be chosen consistently.\n const existingCallInfo = scriptMatch\n ? detectExistingIntlayerCall(\n scriptContent,\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n let ast: any;\n try {\n ast = parse(code);\n } catch (e) {\n console.warn(\n `Svelte extraction: Failed to parse Svelte AST for ${filename}`,\n e\n );\n return null;\n }\n\n // Walk Svelte HTML AST.\n // Svelte 4 used numeric type constants; Svelte 5 uses string type names.\n // We check for both to remain compatible.\n const isTextNode = (node: any) => node.type === 'Text' || node.type === 3;\n const isAttributeNode = (node: any) =>\n node.type === 'Attribute' || node.type === 6;\n const isExpressionTagNode = (node: any): boolean =>\n node.type === 'MustacheTag' ||\n node.type === 8 || // Svelte 4 numeric\n node.type === 'ExpressionTag'; // Svelte 5\n\n const walkSvelte = (node: any) => {\n if (isTextNode(node)) {\n const text = node.data ?? node.content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // Destructured: each property is a plain value → `{key}`.\n // Otherwise use the reactive store subscription `{$content.key}`.\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `{${ref}}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (\n isAttributeNode(node) &&\n (attributesToExtract as readonly string[]).includes(node.name)\n ) {\n if (node.value && node.value.length === 1 && isTextNode(node.value[0])) {\n const text = node.value[0].data ?? node.value[0].content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `${node.name}={${ref}}`,\n key,\n value: text.trim(),\n });\n }\n }\n }\n\n const children =\n node.children ?? node.fragment?.nodes ?? node.fragment?.children;\n\n // Try to handle mixed text + expression children as an insertion\n if (children?.some(isExpressionTagNode)) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (isTextNode(child)) {\n const text = child.data ?? child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (isExpressionTagNode(child)) {\n // Source slice: skip the leading `{` and trailing `}`\n const exprCode = code.slice(child.start + 1, child.end - 1).trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({ type: 'var', value: varName, originalExpr: exprCode });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{${ref}({ ${varArgs} })}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.start,\n end: lastChild.end,\n replacement,\n key,\n value: cleanString,\n });\n\n // Don't recurse into these children\n if (node.attributes) node.attributes.forEach(walkSvelte);\n return;\n }\n }\n }\n\n if (children) children.forEach(walkSvelte);\n if (node.attributes) node.attributes.forEach(walkSvelte);\n };\n\n if (ast.html) {\n walkSvelte(ast.html);\n }\n\n if (scriptMatch) {\n const openTagEndIndex = scriptMatch[0].indexOf('>') + 1;\n const offset = scriptMatch.index + openTagEndIndex;\n\n try {\n const babelAst = babelParse(scriptContent, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (babelAst) {\n traverse(babelAst, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n if (path.parentPath.isExportDeclaration()) return;\n if (path.parentPath.isImportSpecifier()) return;\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n hasScriptExtraction = true;\n\n if (path.node.start != null && path.node.end != null) {\n // Destructured: each property is a plain value → access directly.\n // Otherwise use `get(content).key` to read the Svelte store.\n const ref = isDestructured ? key : `get(${varName}).${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (error) {\n console.warn(\n `Svelte extraction: Failed to parse script content for ${filename}`,\n error\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order (prevents magic-string chunk errors)\n replacements.sort((a, b) => b.start - a.start);\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n scriptContent\n ) || /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(scriptContent);\n\n const hasGetImport =\n /import\\s*{[^}]*get[^}]*}\\s*from\\s*['\"]svelte\\/store['\"]/.test(\n scriptContent\n );\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(scriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const getImportStmt =\n hasScriptExtraction && !isDestructured && !hasGetImport\n ? `import { get } from 'svelte/store';`\n : '';\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, getImportStmt, contentDecl].filter(\n Boolean\n );\n\n if (injectionParts.length > 0) {\n const injection = `\\n ${injectionParts.join('\\n ')}\\n`;\n\n if (scriptMatch) {\n const scriptContentStart =\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1;\n magic.appendLeft(scriptContentStart, injection);\n } else {\n magic.prepend(\n `<script>\\n ${importStmt}\\n ${hasScriptExtraction ? \"import { get } from 'svelte/store';\" : ''}\\n ${contentDecl}\\n</script>\\n\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: extractedContent,\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processSvelteFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> | null = null;\n\n const result = intlayerSvelteExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent: extractedContent!,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"svelte-intlayer-extract.cjs","names":["t","DEFAULT_LOCALE","MagicString"],"sources":["../../src/svelte-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport MagicString from 'magic-string';\nimport { parse } from 'svelte/compiler';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether a script block already contains a `useIntlayer` /\n * `getIntlayer` call and whether its result is destructured.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (p: any): p is typeof t.objectProperty =>\n t.isObjectProperty(p) && t.isIdentifier(p.key)\n )\n .map((p: any) => (p.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\n/* ────────────────────────────────────────── helpers ─────────────────────── */\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\n/* ────────────────────────────────────────── plugin ──────────────────────── */\n\nexport const intlayerSvelteExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'svelte-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.svelte')) return null;\n\n const magic = new MagicString(code);\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Extract and walk Script using Babel\n const scriptRegex = /<script[^>]*>([\\s\\S]*?)<\\/script>/;\n const scriptMatch = scriptRegex.exec(code);\n let hasScriptExtraction = false;\n const scriptContent = scriptMatch ? scriptMatch[1] : '';\n\n // Detect existing call BEFORE walking the template so the access pattern\n // (bare key vs. $content.key) can be chosen consistently.\n const existingCallInfo = scriptMatch\n ? detectExistingIntlayerCall(\n scriptContent,\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n let ast: any;\n try {\n ast = parse(code);\n } catch (e) {\n console.warn(\n `Svelte extraction: Failed to parse Svelte AST for ${filename}`,\n e\n );\n return null;\n }\n\n // Walk Svelte HTML AST.\n // Svelte 4 used numeric type constants; Svelte 5 uses string type names.\n // We check for both to remain compatible.\n const isTextNode = (node: any) => node.type === 'Text' || node.type === 3;\n const isAttributeNode = (node: any) =>\n node.type === 'Attribute' || node.type === 6;\n const isExpressionTagNode = (node: any): boolean =>\n node.type === 'MustacheTag' ||\n node.type === 8 || // Svelte 4 numeric\n node.type === 'ExpressionTag'; // Svelte 5\n\n const walkSvelte = (node: any) => {\n if (isTextNode(node)) {\n const text = node.data ?? node.content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // Destructured: each property is a plain value → `{key}`.\n // Otherwise use the reactive store subscription `{$content.key}`.\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `{${ref}}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (\n isAttributeNode(node) &&\n (attributesToExtract as readonly string[]).includes(node.name)\n ) {\n if (node.value && node.value.length === 1 && isTextNode(node.value[0])) {\n const text = node.value[0].data ?? node.value[0].content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `${node.name}={${ref}}`,\n key,\n value: text.trim(),\n });\n }\n }\n }\n\n const children =\n node.children ?? node.fragment?.nodes ?? node.fragment?.children;\n\n // Try to handle mixed text + expression children as an insertion\n if (children?.some(isExpressionTagNode)) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (isTextNode(child)) {\n const text = child.data ?? child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (isExpressionTagNode(child)) {\n // Source slice: skip the leading `{` and trailing `}`\n const exprCode = code.slice(child.start + 1, child.end - 1).trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({ type: 'var', value: varName, originalExpr: exprCode });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{${ref}({ ${varArgs} })}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.start,\n end: lastChild.end,\n replacement,\n key,\n value: cleanString,\n });\n\n // Don't recurse into these children\n if (node.attributes) node.attributes.forEach(walkSvelte);\n return;\n }\n }\n }\n\n if (children) children.forEach(walkSvelte);\n if (node.attributes) node.attributes.forEach(walkSvelte);\n };\n\n if (ast.html) {\n walkSvelte(ast.html);\n }\n\n if (scriptMatch) {\n const openTagEndIndex = scriptMatch[0].indexOf('>') + 1;\n const offset = scriptMatch.index + openTagEndIndex;\n\n try {\n const babelAst = babelParse(scriptContent, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (babelAst) {\n traverse(babelAst, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n if (path.parentPath.isExportDeclaration()) return;\n if (path.parentPath.isImportSpecifier()) return;\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n hasScriptExtraction = true;\n\n if (path.node.start != null && path.node.end != null) {\n // Destructured: each property is a plain value → access directly.\n // Otherwise use `get(content).key` to read the Svelte store.\n const ref = isDestructured ? key : `get(${varName}).${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (error) {\n console.warn(\n `Svelte extraction: Failed to parse script content for ${filename}`,\n error\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order (prevents magic-string chunk errors)\n replacements.sort((a, b) => b.start - a.start);\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n scriptContent\n ) || /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(scriptContent);\n\n const hasGetImport =\n /import\\s*{[^}]*get[^}]*}\\s*from\\s*['\"]svelte\\/store['\"]/.test(\n scriptContent\n );\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(scriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const getImportStmt =\n hasScriptExtraction && !isDestructured && !hasGetImport\n ? `import { get } from 'svelte/store';`\n : '';\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, getImportStmt, contentDecl].filter(\n Boolean\n );\n\n if (injectionParts.length > 0) {\n const injection = `\\n ${injectionParts.join('\\n ')}\\n`;\n\n if (scriptMatch) {\n const scriptContentStart =\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1;\n magic.appendLeft(scriptContentStart, injection);\n } else {\n magic.prepend(\n `<script>\\n ${importStmt}\\n ${hasScriptExtraction ? \"import { get } from 'svelte/store';\" : ''}\\n ${contentDecl}\\n</script>\\n\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: extractedContent,\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processSvelteFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> | null = null;\n\n const result = intlayerSvelteExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent: extractedContent!,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractSvelteFieldUsage.mjs","names":[],"sources":["../../src/extractSvelteFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Svelte SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Svelte's `useIntlayer` returns a Svelte store. Inside both the `<script>`\n * block and the `<template>`, stores are consumed via the auto-subscription\n * prefix `$` — so `const bm = useIntlayer('benchmark')` is accessed as\n * `$bm.fieldName`.\n *\n * Because Svelte's `$store` syntax is preprocessed before standard JS tools\n * see the file, static Babel-scope analysis cannot link `$bm` back to `bm`.\n * This module fills that gap by running a targeted regex over the raw source.\n */\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`bm` in `const bm = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Analyzes a Svelte SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * Matches the Svelte reactive store access pattern `$varName.fieldName`\n * throughout the entire file (both `<script>` and markup regions).\n *\n * @param code - Full `.svelte` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractSvelteIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // Svelte store auto-subscription: $varName.fieldName\n // The `$` must be preceded by a word boundary or the start of the\n // expression (not an identifier character) to avoid false positives such\n // as matching `$$bm.field` or `_$bm.field`.\n const storeRe = new RegExp(`(?<![\\\\w$])\\\\$${esc}\\\\.(\\\\w+)`, 'g');\n\n storeRe.lastIndex = 0;\n for (let m = storeRe.exec(code); m !== null; m = storeRe.exec(code)) {\n fields.add(m[1]);\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,OAAO;;;;;;;;;;;;;;AAuB5C,MAAa,mCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,KAA0B;
|
|
1
|
+
{"version":3,"file":"extractSvelteFieldUsage.mjs","names":[],"sources":["../../src/extractSvelteFieldUsage.ts"],"sourcesContent":["/**\n * Extracts intlayer dictionary field usage from a Svelte SFC for a set of\n * plain variable bindings (i.e. `const content = useIntlayer('key')`).\n *\n * Svelte's `useIntlayer` returns a Svelte store. Inside both the `<script>`\n * block and the `<template>`, stores are consumed via the auto-subscription\n * prefix `$` — so `const bm = useIntlayer('benchmark')` is accessed as\n * `$bm.fieldName`.\n *\n * Because Svelte's `$store` syntax is preprocessed before standard JS tools\n * see the file, static Babel-scope analysis cannot link `$bm` back to `bm`.\n * This module fills that gap by running a targeted regex over the raw source.\n */\n\n/** Escapes special regex characters in a string used as a regex literal. */\nconst escapeRegExp = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Input descriptor for a single plain variable binding. */\nexport type PlainVariableInfo = {\n /** The local variable name in the script (`bm` in `const bm = useIntlayer(…)`). */\n variableName: string;\n /** The intlayer dictionary key passed to `useIntlayer`. */\n dictionaryKey: string;\n};\n\n/**\n * Analyzes a Svelte SFC source string and returns the top-level content field\n * names that are statically accessed for each plain intlayer variable binding.\n *\n * Matches the Svelte reactive store access pattern `$varName.fieldName`\n * throughout the entire file (both `<script>` and markup regions).\n *\n * @param code - Full `.svelte` file source.\n * @param plainVariables - List of plain variable bindings to analyse.\n * @returns Map from dictionary key to the set of accessed top-level field names.\n * If no fields can be determined for a given key it is omitted from the\n * map so the caller can fall back to `'all'`.\n */\nexport const extractSvelteIntlayerFieldUsage = (\n code: string,\n plainVariables: PlainVariableInfo[]\n): Map<string, Set<string>> => {\n const result = new Map<string, Set<string>>();\n\n if (plainVariables.length === 0) return result;\n\n for (const { variableName, dictionaryKey } of plainVariables) {\n const fields = new Set<string>();\n const esc = escapeRegExp(variableName);\n\n // Svelte store auto-subscription: $varName.fieldName\n // The `$` must be preceded by a word boundary or the start of the\n // expression (not an identifier character) to avoid false positives such\n // as matching `$$bm.field` or `_$bm.field`.\n const storeRe = new RegExp(`(?<![\\\\w$])\\\\$${esc}\\\\.(\\\\w+)`, 'g');\n\n storeRe.lastIndex = 0;\n for (let m = storeRe.exec(code); m !== null; m = storeRe.exec(code)) {\n fields.add(m[1]);\n }\n\n if (fields.size > 0) {\n result.set(dictionaryKey, fields);\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,gBAAgB,QACpB,IAAI,QAAQ,uBAAuB,OAAO;;;;;;;;;;;;;;AAuB5C,MAAa,mCACX,MACA,mBAC6B;CAC7B,MAAM,yBAAS,IAAI,KAA0B;CAE7C,IAAI,eAAe,WAAW,GAAG,OAAO;CAExC,KAAK,MAAM,EAAE,cAAc,mBAAmB,gBAAgB;EAC5D,MAAM,yBAAS,IAAI,KAAa;EAChC,MAAM,MAAM,aAAa,aAAa;EAMtC,MAAM,UAAU,IAAI,OAAO,iBAAiB,IAAI,YAAY,IAAI;EAEhE,QAAQ,YAAY;EACpB,KAAK,IAAI,IAAI,QAAQ,KAAK,KAAK,EAAE,MAAM,MAAM,IAAI,QAAQ,KAAK,KAAK,EACjE,OAAO,IAAI,EAAE,GAAG;EAGlB,IAAI,OAAO,OAAO,GAChB,OAAO,IAAI,eAAe,OAAO;;CAIrC,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte-intlayer-extract.mjs","names":["babelParse","t","parse"],"sources":["../../src/svelte-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport MagicString from 'magic-string';\nimport { parse } from 'svelte/compiler';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether a script block already contains a `useIntlayer` /\n * `getIntlayer` call and whether its result is destructured.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (p: any): p is typeof t.objectProperty =>\n t.isObjectProperty(p) && t.isIdentifier(p.key)\n )\n .map((p: any) => (p.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\n/* ────────────────────────────────────────── helpers ─────────────────────── */\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\n/* ────────────────────────────────────────── plugin ──────────────────────── */\n\nexport const intlayerSvelteExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'svelte-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.svelte')) return null;\n\n const magic = new MagicString(code);\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Extract and walk Script using Babel\n const scriptRegex = /<script[^>]*>([\\s\\S]*?)<\\/script>/;\n const scriptMatch = scriptRegex.exec(code);\n let hasScriptExtraction = false;\n const scriptContent = scriptMatch ? scriptMatch[1] : '';\n\n // Detect existing call BEFORE walking the template so the access pattern\n // (bare key vs. $content.key) can be chosen consistently.\n const existingCallInfo = scriptMatch\n ? detectExistingIntlayerCall(\n scriptContent,\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n let ast: any;\n try {\n ast = parse(code);\n } catch (e) {\n console.warn(\n `Svelte extraction: Failed to parse Svelte AST for ${filename}`,\n e\n );\n return null;\n }\n\n // Walk Svelte HTML AST.\n // Svelte 4 used numeric type constants; Svelte 5 uses string type names.\n // We check for both to remain compatible.\n const isTextNode = (node: any) => node.type === 'Text' || node.type === 3;\n const isAttributeNode = (node: any) =>\n node.type === 'Attribute' || node.type === 6;\n const isExpressionTagNode = (node: any): boolean =>\n node.type === 'MustacheTag' ||\n node.type === 8 || // Svelte 4 numeric\n node.type === 'ExpressionTag'; // Svelte 5\n\n const walkSvelte = (node: any) => {\n if (isTextNode(node)) {\n const text = node.data ?? node.content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // Destructured: each property is a plain value → `{key}`.\n // Otherwise use the reactive store subscription `{$content.key}`.\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `{${ref}}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (\n isAttributeNode(node) &&\n (attributesToExtract as readonly string[]).includes(node.name)\n ) {\n if (node.value && node.value.length === 1 && isTextNode(node.value[0])) {\n const text = node.value[0].data ?? node.value[0].content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `${node.name}={${ref}}`,\n key,\n value: text.trim(),\n });\n }\n }\n }\n\n const children =\n node.children ?? node.fragment?.nodes ?? node.fragment?.children;\n\n // Try to handle mixed text + expression children as an insertion\n if (children?.some(isExpressionTagNode)) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (isTextNode(child)) {\n const text = child.data ?? child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (isExpressionTagNode(child)) {\n // Source slice: skip the leading `{` and trailing `}`\n const exprCode = code.slice(child.start + 1, child.end - 1).trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({ type: 'var', value: varName, originalExpr: exprCode });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{${ref}({ ${varArgs} })}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.start,\n end: lastChild.end,\n replacement,\n key,\n value: cleanString,\n });\n\n // Don't recurse into these children\n if (node.attributes) node.attributes.forEach(walkSvelte);\n return;\n }\n }\n }\n\n if (children) children.forEach(walkSvelte);\n if (node.attributes) node.attributes.forEach(walkSvelte);\n };\n\n if (ast.html) {\n walkSvelte(ast.html);\n }\n\n if (scriptMatch) {\n const openTagEndIndex = scriptMatch[0].indexOf('>') + 1;\n const offset = scriptMatch.index + openTagEndIndex;\n\n try {\n const babelAst = babelParse(scriptContent, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (babelAst) {\n traverse(babelAst, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n if (path.parentPath.isExportDeclaration()) return;\n if (path.parentPath.isImportSpecifier()) return;\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n hasScriptExtraction = true;\n\n if (path.node.start != null && path.node.end != null) {\n // Destructured: each property is a plain value → access directly.\n // Otherwise use `get(content).key` to read the Svelte store.\n const ref = isDestructured ? key : `get(${varName}).${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (error) {\n console.warn(\n `Svelte extraction: Failed to parse script content for ${filename}`,\n error\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order (prevents magic-string chunk errors)\n replacements.sort((a, b) => b.start - a.start);\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n scriptContent\n ) || /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(scriptContent);\n\n const hasGetImport =\n /import\\s*{[^}]*get[^}]*}\\s*from\\s*['\"]svelte\\/store['\"]/.test(\n scriptContent\n );\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(scriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const getImportStmt =\n hasScriptExtraction && !isDestructured && !hasGetImport\n ? `import { get } from 'svelte/store';`\n : '';\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, getImportStmt, contentDecl].filter(\n Boolean\n );\n\n if (injectionParts.length > 0) {\n const injection = `\\n ${injectionParts.join('\\n ')}\\n`;\n\n if (scriptMatch) {\n const scriptContentStart =\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1;\n magic.appendLeft(scriptContentStart, injection);\n } else {\n magic.prepend(\n `<script>\\n ${importStmt}\\n ${hasScriptExtraction ? \"import { get } from 'svelte/store';\" : ''}\\n ${contentDecl}\\n</script>\\n\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: extractedContent,\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processSvelteFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> | null = null;\n\n const result = intlayerSvelteExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent: extractedContent!,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"svelte-intlayer-extract.mjs","names":["babelParse","t","parse"],"sources":["../../src/svelte-intlayer-extract.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { parse as babelParse, types as t, traverse } from '@babel/core';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport MagicString from 'magic-string';\nimport { parse } from 'svelte/compiler';\n\ntype ExistingCallInfo = {\n isDestructured: boolean;\n existingDestructuredKeys: string[];\n /** The variable name used to store the call result (e.g. `t` in `const t = useIntlayer(...)`) */\n variableName: string;\n /** Absolute position of `}` in the full file — only valid when `isDestructured` */\n closingBraceAbsolutePos: number;\n /** Absolute position of end of last property — only valid when `isDestructured` */\n lastPropAbsoluteEnd: number;\n} | null;\n\n/**\n * Detects whether a script block already contains a `useIntlayer` /\n * `getIntlayer` call and whether its result is destructured.\n */\nconst detectExistingIntlayerCall = (\n scriptText: string,\n absoluteOffset: number\n): ExistingCallInfo => {\n let info: ExistingCallInfo = null;\n\n try {\n const ast = babelParse(scriptText, {\n parserOpts: { sourceType: 'module', plugins: ['typescript', 'jsx'] },\n });\n\n if (!ast) return null;\n\n traverse(ast, {\n CallExpression(path: any) {\n const callee = path.node.callee;\n\n if (\n !t.isIdentifier(callee) ||\n (callee.name !== 'useIntlayer' && callee.name !== 'getIntlayer')\n )\n return;\n\n const parent = path.parent;\n\n if (t.isVariableDeclarator(parent) && t.isObjectPattern(parent.id)) {\n const properties = parent.id.properties;\n const existingDestructuredKeys = properties\n .filter(\n (p: any): p is typeof t.objectProperty =>\n t.isObjectProperty(p) && t.isIdentifier(p.key)\n )\n .map((p: any) => (p.key as any).name as string);\n const lastProp = properties[properties.length - 1];\n\n info = {\n isDestructured: true,\n variableName: 'content',\n existingDestructuredKeys,\n closingBraceAbsolutePos: absoluteOffset + (parent.id.end! - 1),\n lastPropAbsoluteEnd: absoluteOffset + lastProp.end!,\n };\n } else {\n const variableName =\n t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)\n ? parent.id.name\n : 'content';\n\n info = {\n isDestructured: false,\n variableName,\n existingDestructuredKeys: [],\n closingBraceAbsolutePos: -1,\n lastPropAbsoluteEnd: -1,\n };\n }\n\n path.stop();\n },\n });\n } catch {\n // Silently ignore parse failures — fall back to no-info\n }\n\n return info;\n};\n\nexport type ExtractedContent = Record<string, string>;\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: ExtractedContent;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n defaultLocale?: Locale;\n packageName?: string;\n filesList?: string[];\n shouldExtract?: (text: string) => boolean;\n onExtract?: (result: ExtractResult) => void;\n dictionaryKey?: string;\n attributesToExtract?: readonly string[];\n extractDictionaryKeyFromPath?: (path: string) => string;\n generateKey?: (text: string, existingKeys: Set<string>) => string;\n};\n\ntype Replacement = {\n start: number;\n end: number;\n replacement: string;\n key: string;\n value: string;\n};\n\n/* ────────────────────────────────────────── helpers ─────────────────────── */\n\nexport const shouldProcessFile = (\n filename: string | undefined,\n filesList?: string[]\n): boolean => {\n if (!filename) return false;\n if (!filesList || filesList.length === 0) return true;\n\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n return filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n};\n\n/* ────────────────────────────────────────── plugin ──────────────────────── */\n\nexport const intlayerSvelteExtract = (\n code: string,\n filename: string,\n options: ExtractPluginOptions = {}\n): { code: string; map?: unknown; extracted: boolean } | null => {\n const {\n defaultLocale = DEFAULT_LOCALE,\n packageName = 'svelte-intlayer',\n filesList,\n shouldExtract,\n onExtract,\n dictionaryKey: dictionaryKeyOption,\n attributesToExtract = [],\n extractDictionaryKeyFromPath,\n generateKey,\n } = options;\n\n if (!shouldProcessFile(filename, filesList)) return null;\n if (!filename.endsWith('.svelte')) return null;\n\n const magic = new MagicString(code);\n const extractedContent: ExtractedContent = {};\n const existingKeys = new Set<string>();\n const dictionaryKey =\n dictionaryKeyOption ?? extractDictionaryKeyFromPath?.(filename) ?? '';\n const replacements: Replacement[] = [];\n\n // Extract and walk Script using Babel\n const scriptRegex = /<script[^>]*>([\\s\\S]*?)<\\/script>/;\n const scriptMatch = scriptRegex.exec(code);\n let hasScriptExtraction = false;\n const scriptContent = scriptMatch ? scriptMatch[1] : '';\n\n // Detect existing call BEFORE walking the template so the access pattern\n // (bare key vs. $content.key) can be chosen consistently.\n const existingCallInfo = scriptMatch\n ? detectExistingIntlayerCall(\n scriptContent,\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1\n )\n : null;\n\n const isDestructured = existingCallInfo?.isDestructured ?? false;\n const varName = existingCallInfo?.variableName ?? 'content';\n\n let ast: any;\n try {\n ast = parse(code);\n } catch (e) {\n console.warn(\n `Svelte extraction: Failed to parse Svelte AST for ${filename}`,\n e\n );\n return null;\n }\n\n // Walk Svelte HTML AST.\n // Svelte 4 used numeric type constants; Svelte 5 uses string type names.\n // We check for both to remain compatible.\n const isTextNode = (node: any) => node.type === 'Text' || node.type === 3;\n const isAttributeNode = (node: any) =>\n node.type === 'Attribute' || node.type === 6;\n const isExpressionTagNode = (node: any): boolean =>\n node.type === 'MustacheTag' ||\n node.type === 8 || // Svelte 4 numeric\n node.type === 'ExpressionTag'; // Svelte 5\n\n const walkSvelte = (node: any) => {\n if (isTextNode(node)) {\n const text = node.data ?? node.content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n // Destructured: each property is a plain value → `{key}`.\n // Otherwise use the reactive store subscription `{$content.key}`.\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `{${ref}}`,\n key,\n value: text.replace(/\\s+/g, ' ').trim(),\n });\n }\n } else if (\n isAttributeNode(node) &&\n (attributesToExtract as readonly string[]).includes(node.name)\n ) {\n if (node.value && node.value.length === 1 && isTextNode(node.value[0])) {\n const text = node.value[0].data ?? node.value[0].content ?? '';\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: `${node.name}={${ref}}`,\n key,\n value: text.trim(),\n });\n }\n }\n }\n\n const children =\n node.children ?? node.fragment?.nodes ?? node.fragment?.children;\n\n // Try to handle mixed text + expression children as an insertion\n if (children?.some(isExpressionTagNode)) {\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr: string;\n }[] = [];\n let hasSignificantText = false;\n let isValid = true;\n\n for (const child of children) {\n if (isTextNode(child)) {\n const text = child.data ?? child.content ?? '';\n if (text.trim().length > 0) hasSignificantText = true;\n parts.push({ type: 'text', value: text, originalExpr: '' });\n } else if (isExpressionTagNode(child)) {\n // Source slice: skip the leading `{` and trailing `}`\n const exprCode = code.slice(child.start + 1, child.end - 1).trim();\n const varName = exprCode.includes('.')\n ? exprCode\n .split('.')\n .pop()!\n .replace(/[^\\w$]/g, '')\n : exprCode;\n parts.push({ type: 'var', value: varName, originalExpr: exprCode });\n } else {\n isValid = false;\n break;\n }\n }\n\n if (\n isValid &&\n hasSignificantText &&\n parts.some((p) => p.type === 'var')\n ) {\n let combined = '';\n for (const p of parts) {\n combined += p.type === 'var' ? `{{${p.value}}}` : p.value;\n }\n const cleanString = combined.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract?.(cleanString) && generateKey) {\n const key = generateKey(cleanString, existingKeys);\n existingKeys.add(key);\n const ref = isDestructured ? key : `$${varName}.${key}`;\n\n const uniqueVarPairs = [\n ...new Set(\n parts\n .filter((p) => p.type === 'var')\n .map((p) => `${p.value}: ${p.originalExpr}`)\n ),\n ];\n const varArgs = uniqueVarPairs.join(', ');\n const replacement = `{${ref}({ ${varArgs} })}`;\n\n const firstChild = children[0];\n const lastChild = children[children.length - 1];\n replacements.push({\n start: firstChild.start,\n end: lastChild.end,\n replacement,\n key,\n value: cleanString,\n });\n\n // Don't recurse into these children\n if (node.attributes) node.attributes.forEach(walkSvelte);\n return;\n }\n }\n }\n\n if (children) children.forEach(walkSvelte);\n if (node.attributes) node.attributes.forEach(walkSvelte);\n };\n\n if (ast.html) {\n walkSvelte(ast.html);\n }\n\n if (scriptMatch) {\n const openTagEndIndex = scriptMatch[0].indexOf('>') + 1;\n const offset = scriptMatch.index + openTagEndIndex;\n\n try {\n const babelAst = babelParse(scriptContent, {\n parserOpts: {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n },\n });\n\n if (babelAst) {\n traverse(babelAst, {\n StringLiteral(path: any) {\n if (path.parentPath.isImportDeclaration()) return;\n if (path.parentPath.isExportDeclaration()) return;\n if (path.parentPath.isImportSpecifier()) return;\n if (path.parentPath.isObjectProperty() && path.key === 'key')\n return;\n\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n )\n return;\n\n if (\n t.isIdentifier(callee) &&\n (callee.name === 'useIntlayer' || callee.name === 't')\n )\n return;\n\n if (callee.type === 'Import') return;\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n if (shouldExtract?.(text) && generateKey) {\n const key = generateKey(text, existingKeys);\n existingKeys.add(key);\n hasScriptExtraction = true;\n\n if (path.node.start != null && path.node.end != null) {\n // Destructured: each property is a plain value → access directly.\n // Otherwise use `get(content).key` to read the Svelte store.\n const ref = isDestructured ? key : `get(${varName}).${key}`;\n replacements.push({\n start: offset + path.node.start,\n end: offset + path.node.end,\n replacement: ref,\n key,\n value: text.trim(),\n });\n }\n }\n },\n });\n }\n } catch (error) {\n console.warn(\n `Svelte extraction: Failed to parse script content for ${filename}`,\n error\n );\n }\n }\n\n // Abort if nothing was extracted\n if (replacements.length === 0) return null;\n\n // Apply Replacements in Reverse Order (prevents magic-string chunk errors)\n replacements.sort((a, b) => b.start - a.start);\n for (const { start, end, replacement, key, value } of replacements) {\n magic.overwrite(start, end, replacement);\n extractedContent[key] = value;\n }\n\n // When the existing call is destructured, inject only the missing keys into\n // the ObjectPattern — no new `content` variable is needed.\n if (\n existingCallInfo?.isDestructured &&\n existingCallInfo.closingBraceAbsolutePos >= 0\n ) {\n const missingKeys = Object.keys(extractedContent).filter(\n (k) => !existingCallInfo.existingDestructuredKeys.includes(k)\n );\n\n if (missingKeys.length > 0) {\n // Insert right after the last property so the space/newline before `}`\n // is naturally preserved: `{ a }` → `{ a, b }`.\n magic.appendLeft(\n existingCallInfo.lastPropAbsoluteEnd,\n `, ${missingKeys.join(', ')}`\n );\n }\n }\n\n // Inject necessary imports and setup\n const hasUseIntlayerImport =\n /import\\s*{[^}]*useIntlayer[^}]*}\\s*from\\s*['\"][^'\"]+['\"]/.test(\n scriptContent\n ) || /import\\s+useIntlayer\\s+from\\s*['\"][^'\"]+['\"]/.test(scriptContent);\n\n const hasGetImport =\n /import\\s*{[^}]*get[^}]*}\\s*from\\s*['\"]svelte\\/store['\"]/.test(\n scriptContent\n );\n\n // An existing call (destructured or not) means no new declaration is needed.\n const hasContentDeclaration =\n existingCallInfo !== null ||\n /const\\s+content\\s*=\\s*useIntlayer\\s*\\(/.test(scriptContent);\n\n const importStmt = hasUseIntlayerImport\n ? ''\n : `import { useIntlayer } from '${packageName}';`;\n const getImportStmt =\n hasScriptExtraction && !isDestructured && !hasGetImport\n ? `import { get } from 'svelte/store';`\n : '';\n const contentDecl = hasContentDeclaration\n ? ''\n : `const content = useIntlayer('${dictionaryKey}');`;\n\n const injectionParts = [importStmt, getImportStmt, contentDecl].filter(\n Boolean\n );\n\n if (injectionParts.length > 0) {\n const injection = `\\n ${injectionParts.join('\\n ')}\\n`;\n\n if (scriptMatch) {\n const scriptContentStart =\n scriptMatch.index + scriptMatch[0].indexOf('>') + 1;\n magic.appendLeft(scriptContentStart, injection);\n } else {\n magic.prepend(\n `<script>\\n ${importStmt}\\n ${hasScriptExtraction ? \"import { get } from 'svelte/store';\" : ''}\\n ${contentDecl}\\n</script>\\n\\n`\n );\n }\n }\n\n if (onExtract) {\n onExtract({\n dictionaryKey,\n filePath: filename,\n content: extractedContent,\n locale: defaultLocale,\n });\n }\n\n return {\n code: magic.toString(),\n map: magic.generateMap({ source: filename, includeContent: true }),\n extracted: true,\n };\n};\n\ntype Tools = {\n generateKey: (text: string, existingKeys: Set<string>) => string;\n shouldExtract: (text: string) => boolean;\n extractDictionaryKeyFromPath: (path: string) => string;\n attributesToExtract: readonly string[];\n extractTsContent: any;\n};\n\nexport const processSvelteFile = (\n filePath: string,\n _componentKey: string,\n packageName: string,\n tools: Tools,\n save: boolean = true,\n providedCode?: string\n): {\n extractedContent: Record<string, string>;\n code: string;\n map?: any;\n} | null => {\n const code = providedCode ?? readFileSync(filePath, 'utf-8');\n let extractedContent: Record<string, string> | null = null;\n\n const result = intlayerSvelteExtract(code, filePath, {\n packageName,\n dictionaryKey: _componentKey,\n shouldExtract: tools.shouldExtract,\n generateKey: tools.generateKey,\n extractDictionaryKeyFromPath: tools.extractDictionaryKeyFromPath,\n attributesToExtract: tools.attributesToExtract,\n onExtract: (extractResult) => {\n extractedContent = extractResult.content;\n },\n });\n\n if (!result) return null;\n\n if (save) {\n writeFileSync(filePath, result.code);\n }\n\n return {\n extractedContent: extractedContent!,\n code: result.code,\n map: result.map,\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/svelte-compiler",
|
|
3
|
-
"version": "8.9.
|
|
3
|
+
"version": "8.9.4-canary.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vite-compatible compiler plugin for Svelte with Intlayer, providing HMR support, file transformation, and optimized dictionary loading for Svelte applications.",
|
|
6
6
|
"keywords": [
|
|
@@ -78,22 +78,22 @@
|
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@babel/core": "7.29.0",
|
|
81
|
-
"@intlayer/types": "8.9.
|
|
81
|
+
"@intlayer/types": "8.9.4-canary.0",
|
|
82
82
|
"fast-glob": "3.3.3",
|
|
83
83
|
"magic-string": "0.30.21"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@intlayer/config": "8.9.
|
|
87
|
-
"@intlayer/types": "8.9.
|
|
86
|
+
"@intlayer/config": "8.9.4-canary.0",
|
|
87
|
+
"@intlayer/types": "8.9.4-canary.0",
|
|
88
88
|
"@types/babel__core": "7.20.5",
|
|
89
89
|
"@types/babel__generator": "7.27.0",
|
|
90
90
|
"@types/babel__traverse": "7.28.0",
|
|
91
|
-
"@types/node": "25.6.
|
|
91
|
+
"@types/node": "25.6.2",
|
|
92
92
|
"@utils/ts-config": "1.0.4",
|
|
93
93
|
"@utils/ts-config-types": "1.0.4",
|
|
94
94
|
"@utils/tsdown-config": "1.0.4",
|
|
95
95
|
"rimraf": "6.1.3",
|
|
96
|
-
"tsdown": "0.
|
|
96
|
+
"tsdown": "0.22.00",
|
|
97
97
|
"typescript": "6.0.3",
|
|
98
98
|
"vitest": "4.1.5"
|
|
99
99
|
},
|