forked from FreeTubeApp/FreeTube
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a polyfill for vue-i18n's
useI18n()
composable (FreeTubeApp#6042)
* Add a polyfill for vue-i18n's useI18n() composable * Address feedback * JSDoc ESLint plugin requires each overload to be in a separate comment block
- Loading branch information
Showing
6 changed files
with
184 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import useI18nPolyfillRule from './use-i18n-polyfill-rule.mjs' | ||
|
||
export default { | ||
meta: { | ||
name: 'eslint-plugin-freetube', | ||
version: '1.0' | ||
}, | ||
rules: { | ||
'use-i18n-polyfill': useI18nPolyfillRule | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { dirname, relative, resolve } from 'path' | ||
|
||
const polyfillPath = resolve(import.meta.dirname, '../../src/renderer/composables/use-i18n-polyfill') | ||
|
||
function getRelativePolyfillPath(filePath) { | ||
const relativePath = relative(dirname(filePath), polyfillPath).replaceAll('\\', '/') | ||
|
||
if (relativePath[0] !== '.') { | ||
return `./${relativePath}` | ||
} | ||
|
||
return relativePath | ||
} | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
export default { | ||
meta: { | ||
type: 'problem', | ||
fixable: 'code' | ||
}, | ||
create(context) { | ||
return { | ||
'ImportDeclaration[source.value="vue-i18n"]'(node) { | ||
const specifierIndex = node.specifiers.findIndex(specifier => specifier.type === 'ImportSpecifier' && specifier.imported.name === 'useI18n') | ||
|
||
if (specifierIndex !== -1) { | ||
context.report({ | ||
node: node.specifiers.length === 1 ? node : node.specifiers[specifierIndex], | ||
message: "Please use FreeTube's useI18n polyfill, as vue-i18n's useI18n composable does not work when the vue-i18n is in legacy mode, which is needed for components using the Options API.", | ||
fix: context.physicalFilename === '<text>' | ||
? undefined | ||
: (fixer) => { | ||
const relativePath = getRelativePolyfillPath(context.physicalFilename) | ||
|
||
// If the import only imports `useI18n`, we can just update the source/from text | ||
// Else we need to create a new import for `useI18n` and remove useI18n from the original one | ||
if (node.specifiers.length === 1) { | ||
return fixer.replaceText(node.source, `'${relativePath}'`) | ||
} else { | ||
const specifier = node.specifiers[specifierIndex] | ||
|
||
let specifierText = 'useI18n' | ||
|
||
if (specifier.imported.name !== specifier.local.name) { | ||
specifierText += ` as ${specifier.local.name}` | ||
} | ||
|
||
return [ | ||
fixer.removeRange([ | ||
specifierIndex === 0 ? specifier.start : node.specifiers[specifierIndex - 1].end, | ||
specifierIndex === node.specifiers.length - 1 ? specifier.end : node.specifiers[specifierIndex + 1].start | ||
]), | ||
fixer.insertTextAfter(node, `\nimport { ${specifierText} } from '${relativePath}'`) | ||
] | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* eslint-disable @intlify/vue-i18n/no-dynamic-keys */ | ||
import { computed } from 'vue' | ||
|
||
import i18n from '../i18n/index' | ||
|
||
/** | ||
* Polyfill for vue-i18n's useI18n composable, as it is not available in Vue 2 | ||
* and doesn't work when vue-i18n 9+ (used for Vue 3) is set to `legacy: true`, | ||
* which is needed for Options API components. | ||
* | ||
* Yes, vue-i18n 9 has an `allowComposition` option, | ||
* but it comes with limitations that this polyfill doesn't have and was removed in vue-i18n 10. | ||
* | ||
* @see https://vue-i18n.intlify.dev/guide/migration/vue3#limitations | ||
* @see https://vue-i18n.intlify.dev/guide/migration/breaking10.html#drop-allowcomposition-option | ||
*/ | ||
export function useI18n() { | ||
const locale = computed({ | ||
get() { | ||
return i18n.locale | ||
}, | ||
set(locale) { | ||
i18n.locale = locale | ||
} | ||
}) | ||
|
||
return { | ||
locale, | ||
t | ||
} | ||
} | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @param {number} plural | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @param {unknown[]} list | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @param {unknown[]} list | ||
* @param {number} plural | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @param {Record<string, unknown>} named | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @overload | ||
* @param {string} key | ||
* @param {Record<string, unknown>} named | ||
* @param {number} plural | ||
* @returns {string} | ||
*/ | ||
|
||
/** | ||
* @param {string} key | ||
* @param {number | unknown[] | Record<string, unknown> | undefined} arg1 | ||
* @param {number | undefined} arg2 | ||
* @returns {string} | ||
*/ | ||
function t(key, arg1, arg2) { | ||
// Remove these lines in the Vue 3 migration and pass all args to the `.t()` call | ||
if (typeof arg1 === 'number') { | ||
return i18n.tc(key, arg1) | ||
} else if (typeof arg2 === 'number') { | ||
return i18n.tc(key, arg2, arg1) | ||
} | ||
|
||
if (arg1 != null) { | ||
return i18n.t(key, arg1) | ||
} | ||
|
||
return i18n.t(key) | ||
} |