Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add keyPrefix support for react-i18next #1176

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/by-frameworks/react-i18next/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"react-i18next"
],
"i18n-ally.namespace": true,
"i18n-ally.defaultNamespace": "translation",
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.keystyle": "nested",
"i18n-ally.keysInUse": [
"description.part2_whatever"
],
"i18n-ally.keyPrefix": true
// "i18n-ally.defaultNamespace": "translation"
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"title": "Home"
"title": "Home",
"description": {
"part1": "To get started, edit <1>src/App.js</1> and save to reload.",
"part2": "Switch language between english and german using buttons above."
}
}
5 changes: 5 additions & 0 deletions examples/by-frameworks/react-i18next/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,8 @@ function Page5() {
)
}

function KeyPrefix() {
const { t } = useTranslation('pages/home', { keyPrefix: 'description' });

t('part1')
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,11 @@
"type": "string",
"description": "%config.default_namespace%"
},
"i18n-ally.keyPrefix": {
"type": "boolean",
"description": "Enable keyPrefix parsing",
"default": false
},
"i18n-ally.derivedKeyRules": {
"deprecationMessage": "Deprecated. Use \"i18n-ally.usage.derivedKeyRules\" instead."
},
Expand Down
7 changes: 6 additions & 1 deletion src/core/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class Config {
'encoding',
'namespace',
'defaultNamespace',
'keyPrefix',
'disablePathParsing',
'readonly',
'languageTagSystem',
Expand Down Expand Up @@ -146,6 +147,10 @@ export class Config {
return this.getConfig<string>('defaultNamespace')
}

static get enableKeyPrefix(): boolean | undefined {
return this.getConfig<boolean>('keyPrefix')
}

static get enabledFrameworks(): string[] | undefined {
let ids = this.getConfig<string | string[]>('enabledFrameworks')
if (!ids || !ids.length)
Expand Down Expand Up @@ -180,7 +185,7 @@ export class Config {
return this.getConfig<SortCompare>('sortCompare') || 'binary'
}

static get sortLocale(): string | undefined{
static get sortLocale(): string | undefined {
return this.getConfig<string>('sortLocale')
}

Expand Down
14 changes: 9 additions & 5 deletions src/core/KeyDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ export class KeyDetector {
return keyRange?.key
}

static getScopedKey(document: TextDocument, position: Position)
{
static getScopedKey(document: TextDocument, position: Position) {
const scopes = Global.enabledFrameworks.flatMap(f => f.getScopeRange(document) || [])
if (scopes.length > 0)
{
if (scopes.length > 0) {
const offset = document.offsetAt(position)
return scopes.filter(s => s.start < offset && offset < s.end).map(s => s.namespace).join('.')
return scopes
.filter(s => s.start < offset && offset < s.end)
.map((s) => {
const key = [s.namespace, s.keyPrefix].filter(Boolean).join('.')
return CurrentFile.loader.rewriteKeys(key, 'reference', { namespace: s.namespace })
})
.join('.')
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/editor/completion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CompletionItemProvider, TextDocument, Position, CompletionItem, CompletionItemKind, languages } from 'vscode'
import { ExtensionModule } from '~/modules'
import { Global, KeyDetector, Loader, CurrentFile, LocaleTree, LocaleNode } from '~/core'
import { Global, KeyDetector, Loader, CurrentFile, LocaleTree, LocaleNode, Config } from '~/core'

class CompletionProvider implements CompletionItemProvider {
public provideCompletionItems(
Expand All @@ -16,17 +16,19 @@ class CompletionProvider implements CompletionItemProvider {
if (key === undefined)
return

const scopedKey = KeyDetector.getScopedKey(document, position)
let scopedKey = KeyDetector.getScopedKey(document, position)

if (!key) {
scopedKey = scopedKey || Config.defaultNamespace

return Object
.values(CurrentFile.loader.keys)
.filter(key => key.startsWith(scopedKey || ''))
.map((key) => {
let resolvedKey = key
if (scopedKey)
{
resolvedKey = key.replace(`${scopedKey}.`, "")
}
resolvedKey = key.replace(`${scopedKey}.`, '')

const item = new CompletionItem(resolvedKey, CompletionItemKind.Text)
item.detail = loader.getValueByKey(key)
return item
Expand Down
1 change: 1 addition & 0 deletions src/frameworks/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ScopeRange {
start: number
end: number
namespace: string
keyPrefix?: string
}

export abstract class Framework {
Expand Down
7 changes: 4 additions & 3 deletions src/frameworks/react-i18next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class ReactI18nextFramework extends Framework {

// Add first namespace as a global scope resetting on each occurrence
// useTranslation(ns1) and useTranslation(['ns1', ...])
const regUse = /useTranslation\(\s*\[?\s*['"`](.*?)['"`]/g
const regUse = /useTranslation\(\s*\[?\s*['"`](?<translationKey>.*?)['"`](,\s*['"`][^"'`]*['"`])*\s*\]?\s*(?:,\s*\{[^}]*keyPrefix\s*:\s*['"`](?<keyPrefix>.*?)['"`][^}]*\})?\s*\)/g
let prevGlobalScope = false
for (const match of text.matchAll(regUse)) {
if (typeof match.index !== 'number')
Expand All @@ -173,12 +173,13 @@ class ReactI18nextFramework extends Framework {
ranges[ranges.length - 1].end = match.index

// start a new scope if namespace is provided
if (match[1]) {
if (match.groups?.translationKey) {
prevGlobalScope = true
ranges.push({
start: match.index,
end: text.length,
namespace: match[1],
namespace: match.groups.translationKey,
keyPrefix: match.groups.keyPrefix,
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/utils/Regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function handleRegexMatch(
const quoted = QUOTE_SYMBOLS.includes(text[start - 1])

const namespace = scope?.namespace || defaultNamespace
const keyPrefix = Config.enableKeyPrefix ? scope?.keyPrefix : undefined

// prevent duplicated detection when multiple frameworks enables at the same time.
if (starts.includes(start))
Expand All @@ -38,7 +39,7 @@ export function handleRegexMatch(
const hasExplicitNamespace = namespaceDelimiters.some(delimiter => key.includes(delimiter))

if (!hasExplicitNamespace && namespace)
key = `${namespace}.${key}`
key = `${namespace}.${keyPrefix ? `${keyPrefix}.` : ''}${key}`

if (dotEnding || !key.endsWith('.')) {
key = CurrentFile.loader.rewriteKeys(key, 'reference', {
Expand Down