diff --git a/src/extractors/getFixedTFunction.ts b/src/extractors/getFixedTFunction.ts index 8654679..878b968 100644 --- a/src/extractors/getFixedTFunction.ts +++ b/src/extractors/getFixedTFunction.ts @@ -67,6 +67,11 @@ export default function extractGetFixedTFunction( const tBinding = id.scope.bindings[id.node.name]; if (!tBinding) return []; + const keyPrefixArgument = path.get('arguments')[2]; + const keyPrefix: string | null = getFirstOrNull( + evaluateIfConfident(keyPrefixArgument), + ); + let keys = Array(); for (const reference of tBinding.referencePaths) { if ( @@ -85,6 +90,7 @@ export default function extractGetFixedTFunction( ...k, parsedOptions: { ...k.parsedOptions, + keyPrefix: k.parsedOptions.keyPrefix || keyPrefix, ns: k.parsedOptions.ns || ns, }, })), diff --git a/src/extractors/tFunction.ts b/src/extractors/tFunction.ts index 19004be..18a4770 100644 --- a/src/extractors/tFunction.ts +++ b/src/extractors/tFunction.ts @@ -50,6 +50,7 @@ function parseTCallOptions( contexts: false, hasCount: false, ns: null, + keyPrefix: null, defaultValue: null, }; @@ -76,6 +77,12 @@ function parseTCallOptions( const defaultValueNodeValue = defaultValueNode.get('value'); res.defaultValue = evaluateIfConfident(defaultValueNodeValue); } + + const keyPrefixNode = findKeyInObjectExpression(path, 'keyPrefix'); + if (keyPrefixNode !== null && keyPrefixNode.isObjectProperty()) { + const keyPrefixNodeValue = keyPrefixNode.get('value'); + res.keyPrefix = evaluateIfConfident(keyPrefixNodeValue); + } } return res; diff --git a/src/extractors/transComponent.ts b/src/extractors/transComponent.ts index 7991e49..2a0d0c4 100644 --- a/src/extractors/transComponent.ts +++ b/src/extractors/transComponent.ts @@ -50,6 +50,7 @@ function parseTransComponentOptions( const res: ExtractedKey['parsedOptions'] = { contexts: false, hasCount: false, + keyPrefix: null, ns: null, defaultValue: null, }; diff --git a/src/extractors/useTranslationHook.ts b/src/extractors/useTranslationHook.ts index 831b795..262d73c 100644 --- a/src/extractors/useTranslationHook.ts +++ b/src/extractors/useTranslationHook.ts @@ -59,6 +59,15 @@ export default function extractUseTranslationHook( const tBinding = id.scope.bindings['t']; if (!tBinding) return []; + let keyPrefix: string | null = null; + + const optionsArgument = path.get('arguments')[1]; + const options = getFirstOrNull(evaluateIfConfident(optionsArgument)); + + if (options) { + keyPrefix = options.keyPrefix || keyPrefix; + } + let keys = Array(); for (const reference of tBinding.referencePaths) { if ( @@ -77,6 +86,7 @@ export default function extractUseTranslationHook( ...k, parsedOptions: { ...k.parsedOptions, + keyPrefix: k.parsedOptions.keyPrefix || keyPrefix, ns: k.parsedOptions.ns || ns, }, })), diff --git a/src/keys.ts b/src/keys.ts index e863863..ee06bf1 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -10,6 +10,7 @@ interface I18NextParsedOptions { contexts: string[] | boolean; hasCount: boolean; ns: string | null; + keyPrefix: string | null; defaultValue: string | null; } @@ -49,6 +50,13 @@ export interface TranslationKey extends ExtractedKey { function parseExtractedKey(key: ExtractedKey, config: Config): TranslationKey { let cleanKey = key.key; + const keyPrefix = key.parsedOptions.keyPrefix; + if (keyPrefix) { + // Imitate behavior of i18next and just connect prefix with key before any other action + const keySeparator = config.keySeparator || '.'; + cleanKey = `${keyPrefix}${keySeparator}${cleanKey}`; + } + let ns: string = key.parsedOptions.ns || config.defaultNS; if (config.nsSeparator) { const nsSeparatorPos = cleanKey.indexOf(config.nsSeparator); @@ -60,9 +68,10 @@ function parseExtractedKey(key: ExtractedKey, config: Config): TranslationKey { } let keyPath = Array(); + if (config.keySeparator) { const fullPath = cleanKey.split(config.keySeparator); - keyPath = fullPath.slice(0, fullPath.length - 1); + keyPath = [...keyPath, ...fullPath.slice(0, fullPath.length - 1)]; cleanKey = fullPath[fullPath.length - 1]; } diff --git a/tests/__fixtures__/testGetFixedTFunction/keyPrefix.js b/tests/__fixtures__/testGetFixedTFunction/keyPrefix.js new file mode 100644 index 0000000..00fcba4 --- /dev/null +++ b/tests/__fixtures__/testGetFixedTFunction/keyPrefix.js @@ -0,0 +1,18 @@ +import i18next from "i18next"; + +export function anyJSFunction1() { + const t = i18next.getFixedT(null, 'ns0', 'deep0'); + t('key0'); +} + +export function anyJSFunction2() { + const t = i18next.getFixedT(null, 'ns1', 'deep1.deep2'); + + t('key1'); +} + +export function anyJSFunction3() { + const t = i18next.getFixedT(null, 'ns2', 'deep3.deep4'); + + t('key2.key3'); +} diff --git a/tests/__fixtures__/testGetFixedTFunction/keyPrefix.json b/tests/__fixtures__/testGetFixedTFunction/keyPrefix.json new file mode 100644 index 0000000..1f118c9 --- /dev/null +++ b/tests/__fixtures__/testGetFixedTFunction/keyPrefix.json @@ -0,0 +1,9 @@ +{ + "description": "test keyPrefix usage of getFixedT TFunction", + "pluginOptions": {}, + "expectValues": [ + [{"deep0": { "key0": "" }}, {"ns": "ns0"}], + [{"deep1": { "deep2": { "key1": "" }}}, {"ns": "ns1"}], + [{"deep3": { "deep4": { "key2": { "key3": "" } }}}, {"ns": "ns2"}] + ] +} diff --git a/tests/__fixtures__/testUseTranslationHook/keyPrefix.js b/tests/__fixtures__/testUseTranslationHook/keyPrefix.js new file mode 100644 index 0000000..c6704ed --- /dev/null +++ b/tests/__fixtures__/testUseTranslationHook/keyPrefix.js @@ -0,0 +1,26 @@ +import { useTranslation } from 'react-i18next'; + +export function MyComponent1() { + const [t] = useTranslation('ns0', { keyPrefix: 'deep0' }); + return

{t('key0')}{t('key1')}

+} + +export function MyComponent2() { + const [t] = useTranslation('ns1', { keyPrefix: 'deep1.deep2' }); + return

{t('key2')}{t('key3')}

+} + +export function MyComponent3() { + const [t] = useTranslation('ns2', { keyPrefix: 'deep3.deep4' }); + return

{t('key4.key5')}{t('key4.key6')}

+} + +export function MyComponent4() { + const [t] = useTranslation('ns3', { keyPrefix: 'deep5.deep6' }); + return

{t('key7.key8')}{t('key9.key10')}

+} + +export function MyComponent5() { + const [t] = useTranslation('ns4', { keyPrefix: 'deep7.deep8' }); + return

{t('ns5:key11')}

+} diff --git a/tests/__fixtures__/testUseTranslationHook/keyPrefix.json b/tests/__fixtures__/testUseTranslationHook/keyPrefix.json new file mode 100644 index 0000000..e30ef4f --- /dev/null +++ b/tests/__fixtures__/testUseTranslationHook/keyPrefix.json @@ -0,0 +1,11 @@ +{ + "description": "test keyPrefix extraction of useTranslation hook", + "pluginOptions": {}, + "expectValues": [ + [{ "deep0": {"key0": "", "key1": ""}}, {"ns": "ns0"}], + [{ "deep1": {"deep2": {"key2": "", "key3": ""}}}, {"ns": "ns1"}], + [{ "deep3": {"deep4": {"key4": { "key5": "", "key6": "" }}}}, {"ns": "ns2"}], + [{ "deep5": {"deep6": {"key7": { "key8": "" }, "key9": {"key10": ""}}}}, {"ns": "ns3"}], + [{ "key11": ""}, {"ns": "deep7.deep8.ns5"}] + ] +} diff --git a/tests/__fixtures__/testUseTranslationHook/simple.js b/tests/__fixtures__/testUseTranslationHook/simple.js index 48af0fc..ef5d94c 100644 --- a/tests/__fixtures__/testUseTranslationHook/simple.js +++ b/tests/__fixtures__/testUseTranslationHook/simple.js @@ -18,12 +18,17 @@ const MyComponent2 = () => { return

{t('key2')}

} -export function MyComponent3() { +const MyComponent3 = () => { + const { t, i18n } = useTranslation(); + return

{t('key3.key4')}

+} + +export function MyComponent4() { const {i18n} = useTranslation(); return

Shouldn't crash

} -export function MyComponent4() { +export function MyComponent5() { const { t } = ReactI18Next.useTranslation(); t('from wildcard import'); } diff --git a/tests/__fixtures__/testUseTranslationHook/simple.json b/tests/__fixtures__/testUseTranslationHook/simple.json index 6958643..4d2ed0a 100644 --- a/tests/__fixtures__/testUseTranslationHook/simple.json +++ b/tests/__fixtures__/testUseTranslationHook/simple.json @@ -5,6 +5,7 @@ "key0": "", "key1": "", "key2": "", + "key3": { "key4": "" }, "from wildcard import": "" } } diff --git a/tests/helpers.ts b/tests/helpers.ts index 40ad9ab..8702a40 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -13,6 +13,7 @@ export function createTranslationKey( parsedOptions: { contexts: false, hasCount: false, + keyPrefix: null, ns: null, defaultValue: null, },