From 1a4820d90862b8b180f34d7af9316574d31cdf5f Mon Sep 17 00:00:00 2001 From: Guilherme Eduardo Date: Fri, 16 Aug 2024 20:56:01 -0400 Subject: [PATCH 1/2] fix: add support to custom t names on custom translation hooks --- src/extractors/useTranslationHook.ts | 6 ++++- .../testCustomHook/customNames.js | 26 +++++++++++++++++++ .../testCustomHook/customNames.json | 17 ++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/__fixtures__/testCustomHook/customNames.js create mode 100644 tests/__fixtures__/testCustomHook/customNames.json diff --git a/src/extractors/useTranslationHook.ts b/src/extractors/useTranslationHook.ts index 262d73c..2bbb14f 100644 --- a/src/extractors/useTranslationHook.ts +++ b/src/extractors/useTranslationHook.ts @@ -56,7 +56,11 @@ export default function extractUseTranslationHook( const id = parentPath.get('id'); - const tBinding = id.scope.bindings['t']; + const tBindingKey = + config.tFunctionNames.find((name) => id.scope.bindings[name]) || 't'; + if (!tBindingKey) return []; + + const tBinding = id.scope.bindings[tBindingKey]; if (!tBinding) return []; let keyPrefix: string | null = null; diff --git a/tests/__fixtures__/testCustomHook/customNames.js b/tests/__fixtures__/testCustomHook/customNames.js new file mode 100644 index 0000000..d1b540a --- /dev/null +++ b/tests/__fixtures__/testCustomHook/customNames.js @@ -0,0 +1,26 @@ +import { useMyTranslation, useOtherTranslation } from './i18n'; +import { useThirdPartyTranslation } from 'third-party-module'; +import * as I18Next from 'third-party-module' + +export function MyComponent0() { + const [_] = useMyTranslation('ns0'); + return

{_('key0')}{_('key1')}

+} + +export function MyComponent1() { + const [iceT] = useOtherTranslation(['ns1', 'noob']); + return

{iceT('key in ns1')}

+} + +export function MyComponent2() { + const { translate } = I18Next.useThirdPartyTranslation('ns2'); + someFunc(translate); + return

{translate('key in ns2')}

+} + +export function MyComponent3() { + const foo = 'noob'; + // i18next-extract-mark-ns-next-line ns3 + const [myT] = useThirdPartyTranslation(foo); + return

{myT('key in ns3')}

+} diff --git a/tests/__fixtures__/testCustomHook/customNames.json b/tests/__fixtures__/testCustomHook/customNames.json new file mode 100644 index 0000000..e290b46 --- /dev/null +++ b/tests/__fixtures__/testCustomHook/customNames.json @@ -0,0 +1,17 @@ +{ + "description": "test custom useTranslation hooks giving custom instance name to t function", + "pluginOptions": { + "customUseTranslationHooks": [ + ["./tests/__fixtures__/testCustomHook/i18n", "useMyTranslation"], + ["./tests/__fixtures__/testCustomHook/i18n", "useOtherTranslation"], + ["third-party-module", "useThirdPartyTranslation"] + ], + "tFunctionNames": ["_", "iceT", "myT", "translate"] + }, + "expectValues": [ + [{ "key0": "", "key1": "" }, { "ns": "ns0" }], + [{ "key in ns1": "" }, { "ns": "ns1" }], + [{ "key in ns2": "" }, { "ns": "ns2" }], + [{ "key in ns3": "" }, { "ns": "ns3" }] + ] +} From 5f47b46b1a4ca26b7fa79cc30ea7b2c33f0979cd Mon Sep 17 00:00:00 2001 From: Nourman Hajar Date: Sat, 7 Dec 2024 21:43:53 +0700 Subject: [PATCH 2/2] feat: support aliased t functions from useTranslation hooks --- src/extractors/commons.ts | 44 +++++++++++++++++++ src/extractors/useTranslationHook.ts | 8 ++-- .../testCustomHook/customNames.js | 6 +++ .../testCustomHook/customNames.json | 3 +- .../__fixtures__/testCustomHook/namespace.js | 6 +++ .../testCustomHook/namespace.json | 3 +- .../testUseTranslationHook/keyPrefix.js | 6 +++ .../testUseTranslationHook/keyPrefix.json | 3 +- .../testUseTranslationHook/namespace.js | 5 +++ .../testUseTranslationHook/namespace.json | 3 +- .../testUseTranslationHook/simple.js | 10 +++++ .../testUseTranslationHook/simple.json | 4 +- 12 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/extractors/commons.ts b/src/extractors/commons.ts index 214ade4..7a44cbb 100644 --- a/src/extractors/commons.ts +++ b/src/extractors/commons.ts @@ -298,3 +298,47 @@ export function isCustomImportedNode( return referencesImport(name, sourceModule, importName); }); } + +/** + * Find the aliased t function name (after being destructured). + * If the destructure `t` function is not aliased, will return the identifier name as it is. + * + * For instance, given the following code: + * const { t: tCommon } = useTranslation('common'); + * return

{tCommon('key1')}

+ * + * // or with pluginOptions.tFunctionNames = ["myT"] + * const { myT: tCommon } = useTranslation('common'); + * return

{tCommon('key1')}

+ * + * getAliasedTBindingName(nodePath) should return 'tCommon' instead of t or myT + * + * @param nodePath: node path to resolve + * @param tFunctionNames: possible names for the (unaliased) t function + * @return the resolved t binding name, returning the alias if needed + */ +export function getAliasedTBindingName( + path: BabelCore.NodePath, + tFunctionNames: string[], +): string | undefined { + const properties = path.get('properties'); + const propertiesArray = Array.isArray(properties) + ? properties + : [properties]; + + for (const property of propertiesArray) { + if (property.isObjectProperty()) { + const key = property.node.key; + const value = property.node.value; + if ( + key.type === 'Identifier' && + value.type === 'Identifier' && + tFunctionNames.includes(key.name) + ) { + return value.name; + } + } + } + + return tFunctionNames.find((name) => path.scope.bindings[name]); +} diff --git a/src/extractors/useTranslationHook.ts b/src/extractors/useTranslationHook.ts index 2bbb14f..c7f7fde 100644 --- a/src/extractors/useTranslationHook.ts +++ b/src/extractors/useTranslationHook.ts @@ -9,6 +9,7 @@ import { getFirstOrNull, evaluateIfConfident, referencesImport, + getAliasedTBindingName, } from './commons'; import extractTFunction from './tFunction'; @@ -56,11 +57,10 @@ export default function extractUseTranslationHook( const id = parentPath.get('id'); - const tBindingKey = - config.tFunctionNames.find((name) => id.scope.bindings[name]) || 't'; - if (!tBindingKey) return []; + const tBindingName = getAliasedTBindingName(id, config.tFunctionNames); + if (!tBindingName) return []; - const tBinding = id.scope.bindings[tBindingKey]; + const tBinding = id.scope.bindings[tBindingName]; if (!tBinding) return []; let keyPrefix: string | null = null; diff --git a/tests/__fixtures__/testCustomHook/customNames.js b/tests/__fixtures__/testCustomHook/customNames.js index d1b540a..3a83427 100644 --- a/tests/__fixtures__/testCustomHook/customNames.js +++ b/tests/__fixtures__/testCustomHook/customNames.js @@ -24,3 +24,9 @@ export function MyComponent3() { const [myT] = useThirdPartyTranslation(foo); return

{myT('key in ns3')}

} + +export function MyComponent4() { + const { translate: aliasedTranslate } = I18Next.useThirdPartyTranslation('ns4'); + someFunc(aliasedTranslate); + return

{aliasedTranslate('key in ns4')}

+} diff --git a/tests/__fixtures__/testCustomHook/customNames.json b/tests/__fixtures__/testCustomHook/customNames.json index e290b46..9e25eec 100644 --- a/tests/__fixtures__/testCustomHook/customNames.json +++ b/tests/__fixtures__/testCustomHook/customNames.json @@ -12,6 +12,7 @@ [{ "key0": "", "key1": "" }, { "ns": "ns0" }], [{ "key in ns1": "" }, { "ns": "ns1" }], [{ "key in ns2": "" }, { "ns": "ns2" }], - [{ "key in ns3": "" }, { "ns": "ns3" }] + [{ "key in ns3": "" }, { "ns": "ns3" }], + [{ "key in ns4": "" }, { "ns": "ns4" }] ] } diff --git a/tests/__fixtures__/testCustomHook/namespace.js b/tests/__fixtures__/testCustomHook/namespace.js index 1519cf3..aa675f2 100644 --- a/tests/__fixtures__/testCustomHook/namespace.js +++ b/tests/__fixtures__/testCustomHook/namespace.js @@ -24,3 +24,9 @@ export function MyComponent3() { const [t] = useThirdPartyTranslation(foo); return

{t('key in ns3')}

} + +export function MyComponent4() { + const { t: aliasedT } = I18Next.useThirdPartyTranslation('ns4'); + someFunc(t); + return

{aliasedT('key in ns4')}

+} diff --git a/tests/__fixtures__/testCustomHook/namespace.json b/tests/__fixtures__/testCustomHook/namespace.json index 213008b..d949205 100644 --- a/tests/__fixtures__/testCustomHook/namespace.json +++ b/tests/__fixtures__/testCustomHook/namespace.json @@ -11,6 +11,7 @@ [{"key0": "", "key1": ""}, {"ns": "ns0"}], [{"key in ns1": ""}, {"ns": "ns1"}], [{"key in ns2": ""}, {"ns": "ns2"}], - [{"key in ns3": ""}, {"ns": "ns3"}] + [{"key in ns3": ""}, {"ns": "ns3"}], + [{"key in ns4": ""}, {"ns": "ns4"}] ] } diff --git a/tests/__fixtures__/testUseTranslationHook/keyPrefix.js b/tests/__fixtures__/testUseTranslationHook/keyPrefix.js index c6704ed..c3a404c 100644 --- a/tests/__fixtures__/testUseTranslationHook/keyPrefix.js +++ b/tests/__fixtures__/testUseTranslationHook/keyPrefix.js @@ -24,3 +24,9 @@ export function MyComponent5() { const [t] = useTranslation('ns4', { keyPrefix: 'deep7.deep8' }); return

{t('ns5:key11')}

} + +export function MyComponent6() { + const { t: aliasedT } = useTranslation('ns6', { keyPrefix: 'deep1.deep2' }); + return

{aliasedT('key12')}

+} + diff --git a/tests/__fixtures__/testUseTranslationHook/keyPrefix.json b/tests/__fixtures__/testUseTranslationHook/keyPrefix.json index e30ef4f..052c01b 100644 --- a/tests/__fixtures__/testUseTranslationHook/keyPrefix.json +++ b/tests/__fixtures__/testUseTranslationHook/keyPrefix.json @@ -6,6 +6,7 @@ [{ "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"}] + [{ "key11": ""}, {"ns": "deep7.deep8.ns5"}], + [{ "deep1": {"deep2": {"key12": ""}}}, {"ns": "ns6"}] ] } diff --git a/tests/__fixtures__/testUseTranslationHook/namespace.js b/tests/__fixtures__/testUseTranslationHook/namespace.js index 649106e..eeda1cc 100644 --- a/tests/__fixtures__/testUseTranslationHook/namespace.js +++ b/tests/__fixtures__/testUseTranslationHook/namespace.js @@ -23,3 +23,8 @@ export function MyComponent4() { someFunc(t); return

{t('key0')}

} + +export function MyComponent5() { + const { t: aliasedT } = useTranslation('ns4'); + return

{aliasedT('key0')}

+} diff --git a/tests/__fixtures__/testUseTranslationHook/namespace.json b/tests/__fixtures__/testUseTranslationHook/namespace.json index e8736eb..3012328 100644 --- a/tests/__fixtures__/testUseTranslationHook/namespace.json +++ b/tests/__fixtures__/testUseTranslationHook/namespace.json @@ -5,6 +5,7 @@ [{"key0": "", "key1": ""}, {"ns": "ns0"}], [{"key0": ""}, {"ns": "ns1"}], [{"key0": ""}, {"ns": "ns2"}], - [{"key0": ""}, {"ns": "ns3"}] + [{"key0": ""}, {"ns": "ns3"}], + [{"key0": ""}, {"ns": "ns4"}] ] } diff --git a/tests/__fixtures__/testUseTranslationHook/simple.js b/tests/__fixtures__/testUseTranslationHook/simple.js index ef5d94c..3a56237 100644 --- a/tests/__fixtures__/testUseTranslationHook/simple.js +++ b/tests/__fixtures__/testUseTranslationHook/simple.js @@ -32,3 +32,13 @@ export function MyComponent5() { const { t } = ReactI18Next.useTranslation(); t('from wildcard import'); } + +export function MyComponent6() { + const { t: aliasedT } = ReactI18Next.useTranslation(); + aliasedT('from wildcard import but aliased'); +} + +export const MyComponent7 = () => { + const { t: aliasedT } = useTranslation(); + return

{aliasedT('key5')}

+} diff --git a/tests/__fixtures__/testUseTranslationHook/simple.json b/tests/__fixtures__/testUseTranslationHook/simple.json index 4d2ed0a..ce00b93 100644 --- a/tests/__fixtures__/testUseTranslationHook/simple.json +++ b/tests/__fixtures__/testUseTranslationHook/simple.json @@ -6,6 +6,8 @@ "key1": "", "key2": "", "key3": { "key4": "" }, - "from wildcard import": "" + "from wildcard import": "", + "from wildcard import but aliased": "", + "key5": "" } }