/ Webpack i18n plugin

Webpack i18n plugin

一个 Webpack 插件,用于将本地化函数 (i18n) 的调用替换为目标文本。

🌍 webpack-i18n-assets-plugin

一个 Webpack 插件,用于将本地化函数 (i18n) 的调用替换为目标文本。

特性

  • 将 i18n 文本内联到 bundle 中(同时将参数替换到最终字符串中)
  • 在一次构建中为所有 locale 生成资源
  • 该插件仅适用于生产构建!
  • 只支持本地化函数参数中的字面量作为键(不允许使用模板字符串和变量)

📝 如何使用

  1. 安装包:

    npm i -D @gravity-ui/webpack-i18n-assets-plugin
    
  2. 将插件连接到 Webpack(以 @gravity-ui/app-builder 为例):

    Webpack 配置示例 (webpack.config.js):

    const {I18nAssetsPlugin} = require('@gravity-ui/webpack-i18n-assets-plugin');
    
    // 示例。读取所有包含本地化文本的文件并存储在此映射中
    const locales = {
        en: {},
        ru: {},
        tr: {},
    };
    
    module.exports = {
        output: {
            filename: '[name].[locale].js', // 文件名中需要 [locale]
        },
    
        plugins: [
            new I18nAssetsPlugin({
                locales
            })
        ]
    }
    

    如果您想为每个 locale 创建资源清单(assets manifests)的示例 (webpack.config.js):

    const {applyPluginToWebpackConfig} = require('@gravity-ui/webpack-i18n-assets-plugin');
    
    const locales = {
        en: {},
        ru: {},
        tr: {},
    };
    
    // 一些现有的 webpack 配置
    const webpackConfig = {
        plugins: [ ... ],
        ...
    };
    
    // 使用 applyPluginToWebpackConfig 时,还会连接 WebpackAssetsManifest 插件,
    // 该插件将为每个 locale 生成资源清单。
    module.exports = applyPluginToWebpackConfig(webpackConfig, {locales});
    

    如果您使用 @gravity-ui/app-builder 的示例:

    import type {ServiceConfig} from '@gravity-ui/app-builder';
    import {applyPluginToWebpackConfig, Options} from '@gravity-ui/webpack-i18n-assets-plugin';
    
    const locales = {
        en: {},
        ru: {},
        tr: {},
    };
    
    // 使用 applyPluginToWebpackConfig 时,还会连接 WebpackAssetsManifest 插件,
    // 该插件将为每个 locale 生成资源清单。
    const config: ServiceConfig = {
        client: {
            webpack: (originalConfig) => applyPluginToWebpackConfig(originalConfig, {locales}),
        },
    }
    
  3. 在服务器上配置动态静态资源(使用 @gravity-ui/app-layout 的示例):

    import {createRenderFunction, createLayoutPlugin} from '@gravity-ui/app-layout';
    
    const renderLayout = createRenderFunction([
        createLayoutPlugin({
            manifest: ({lang = 'en'}) => {
                return `assets-manifest.${lang}.json`;
            },
            publicPath: '/build/',
        }),
    ]);
    
    app.get((req, res) => {
        res.send(
            renderLayout({
                title: 'Home page',
                pluginsOptions: {
                    layout: {
                        name: 'home',
                    },
                },
            }),
        );
    });
    

🔧 设置

默认情况下,该插件配置为与 @gravity-ui/i18n 库一起工作,但您可以为任何其他 i18n 库自定义处理方式。

importResolver

类型:ImportResolver

处理导入并标记哪些导入应被视为本地化函数的函数(之后,对标记标识符的调用由 replacer 处理)。

签名与 webpack 的原始 importSpecifier 类似。

示例:

const importResolver = (source: string, exportName: string, _identifierName: string, module: string) => {
    // 如果需要忽略基于特定路径的模块处理,可以这样处理。
    if (module.startsWith('src/units/compute')) {
        return undefined;
    }

    // 处理全局函数的默认导入
    // import i18n from 'ui/utils/i18n'
    if (source === 'ui/utils/i18n' && exportName === 'default') {
        return {
            resolved: true,
            keyset: undefined,
        };
    }

    // 处理辅助函数的导入,并指定它属于公共 keyset(命名空间)。
    // import {ci18n} from 'ui/utils/i18n'
    if (source === 'ui/utils/i18n' && exportName === 'ci18n') {
        return {
            resolved: true,
            keyset: 'common',
        };
    }

    return undefined;
};

declarationResolver

类型:DeclarationResolver

处理变量声明并标记哪些变量应被视为本地化函数的函数(之后,对标记标识符的调用由 replacer 函数处理)。

示例:

import type {VariableDeclarator} from 'estree';

const declarationResolver = (declarator: VariableDeclarator, module: string) => {
    // 如果需要忽略基于特定路径的模块处理,可以这样处理。
    if (module.startsWith('src/units/compute')) {
        return undefined;
    }
    // 处理函数声明,例如 const i18nK = i18n.bind(null, 'keyset');
    if (
        declarator.id.type === 'Identifier' &&
        declarator.id.name.startsWith('i18n') &&
        declarator.init &&
        isI18nBind(declarator.init)
    ) {
        return {
            functionName: declarator.id.name,
            keyset: getKeysetFromBind(declarator.init),
        };
    }

    return undefined;
};

replacer

类型:Replacer

一个处理本地化函数调用并返回字符串替换的函数。

示例:

import type {VariableDeclarator} from 'estree';
import type {ReplacerArgs, ReplacerContext} from '@gravity-ui/webpack-i18n-assets-plugin';

function replacer(
    this: ReplacerContext,
    {callNode, key: parsedKey, keyset: parsedKeyset, localeName}: ReplacerArgs,
) => {
    let key = parsedKey;
    let keyset = parsedKeyset;
    let params: Expression | SpreadElement | undefined;

    const getStringValue = (node: Expression | SpreadElement) => {
        if (node.type === 'Literal' && typeof node.value === 'string') {
            return node.value;
        }

        throw new Error('Incorrect argument type in localizer call');
    };

    // 处理带有一个参数的调用 i18nK('key')
    if (callNode.arguments.length === 1) {
        key = getStringValue(callNode.arguments[0]);
    } else if (callNode.arguments.length === 2) {
        // 处理 i18n('keyset', 'key') 或 i18nK('key', {params})
        const [firstArg, secondArg] = callNode.arguments;

        // 调用 i18n('keyset', 'key')
        if (secondArg.type === 'Literal') {
            keyset = getStringValue(firstArg);
            key = getStringValue(secondArg);
        } else {
            // 调用 i18nK('key', {params})
            key = getStringValue(firstArg);
            params = secondArg;
        }
    } else if (callNode.arguments.length === 3) {
        // 调用 i18n(namespace, key, params)
        const [firstArg, secondArg, thirdArg] = callNode.arguments;
        keyset = getStringValue(firstArg);
        key = getStringValue(secondArg);
        params = thirdArg;
    } else {
        throw new Error('Incorrect count of arguments in localizer call');
    }

    // 务必处理从函数调用参数中获取的 key。
    // 如果函数与 keyset 相关,修改代码后,keyset 可以插入到 key 中(这是插件的功能)。
    // 如果使用来自 ReplacerArgs 的 key,它不带 keyset,不需要处理。
    const keyParts = key.split('::');
    if (keyParts.length === 2) {
        key = keyParts[1];
    }

    const value = this.resolveKey(key, keyset);

    // 在此处根据您的需求实现替换选项。
    // 例如,如果 key 是复数形式,则返回一个函数调用等。

    return JSON.stringify(value);
};

collectUnusedKeys

类型:[Boolean] (默认 - false)

启用收集项目中未使用的 key 的模式。构建后,它会创建一个名为 unused-keys.json 的文件。

为确保功能正常,在 Replacer 函数中始终返回详细格式是必要的。这一点很重要,因为在替换过程中,有可能修改自动确定的 key 和 keyset。

Frameworks settings

Gravity i18n

用于处理来自 @gravity-ui/i18n 库的本地化函数调用的函数。

现成的函数位于 此处

函数将处理的代码示例:

// importResolver 只考虑 ui/utils/i18n 路径上的默认导入。
import i18n from 'ui/utils/i18n';

// declarationResolver 处理值为 i18n.bind 调用的变量。
const i18nK = i18n.bind(null, 'component.navigation');

// replacer 处理由 importResolver 和 declarationResolver 找到的标识符的调用
// 这意味着以下调用将被处理:
i18nK('some_key');
i18nK('some_plural_key', { count: 123 });
i18nK('some_key_with_param', { someParam: 'hello' });
i18n('component.navigation', 'some_key');
i18n('component.navigation', 'some_plural_key', { count: 123 });
i18n('component.navigation', 'some_key_with_param', { someParam: 'hello' });

Replacer 额外执行以下操作:

  1. 将参数内联到字符串中。例如,如果 key 的值为:

    const keyset = {
        some_key: 'string value with {{param}}'
    };
    
    i18nK('some_key', {param: getSomeParam()})
    // 替换后,我们将得到:
    `string value with ${getSomeParam()}`
    
  2. 为复数 key 替换自调用函数:

    const keyset = {
        pural_key: [
            'one_form {{count}}',
            'few_form {{count}}',
            'many_form {{count}}',
            'other_form {{count}}',
        ],
    };
    
    i18nK('pural_key', {count: getSomeCount()})
    
    // 替换后,我们将得到:
    (function(f,c){
        const v=f[!c ? "zero" : new Intl.PluralRules("${locale}").select(c)];
        return v && v.replaceAll("{{count}}",c);
    })({
        "one": "one_form {{count}}",
        "few": "few_form {{count}}",
        "many": "many_form {{count}}",
        "other": "other_form {{count}}"
    }, getSomeCount())
    

ℹ️ FAQ

webpack-localize-assets-plugin 相比如何?

本插件的实现借鉴了 webpack-localize-assets-plugins 包中的一些想法(非常感谢该包的创建者!)。

区别如下:

  • 更便捷的 API,允许您使用任何类型的国际化函数(包括命名空间助手,如 i18next 的 useTranslation,或其他模块导入的函数等)。
  • 正确生成相对于源代码的源映射。
  • 仅支持 webpack 5。已移除对 webpack 4 的支持。
关于库
用星标支持该库
版本
1.0.0
最后更新
19.08.2024
代码仓库
github.com/gravity-ui/webpack-i18n-assets-plugin
许可证
MIT License
维护者
贡献者