@gtkx/cli 1.6.0 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -44
- package/bin/gtkx-dev-runner.js +5 -1
- package/bin/gtkx.js +5 -1
- package/dist/builder.d.ts +1 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +44 -12
- package/dist/builder.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/codegen/config-dependencies.d.ts +3 -0
- package/dist/codegen/config-dependencies.d.ts.map +1 -0
- package/dist/codegen/config-dependencies.js +355 -0
- package/dist/codegen/config-dependencies.js.map +1 -0
- package/dist/codegen/freshness.d.ts.map +1 -1
- package/dist/codegen/freshness.js +49 -16
- package/dist/codegen/freshness.js.map +1 -1
- package/dist/codegen/reference.d.ts.map +1 -1
- package/dist/codegen/reference.js +2 -7
- package/dist/codegen/reference.js.map +1 -1
- package/dist/codegen/report.d.ts.map +1 -1
- package/dist/codegen/report.js +5 -16
- package/dist/codegen/report.js.map +1 -1
- package/dist/codegen/run-codegen.d.ts +9 -8
- package/dist/codegen/run-codegen.d.ts.map +1 -1
- package/dist/codegen/run-codegen.js +60 -113
- package/dist/codegen/run-codegen.js.map +1 -1
- package/dist/codegen/store-resolver.d.ts +3 -2
- package/dist/codegen/store-resolver.d.ts.map +1 -1
- package/dist/codegen/store-resolver.js +14 -20
- package/dist/codegen/store-resolver.js.map +1 -1
- package/dist/commands/build.d.ts +5 -0
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +83 -10
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cleanup.d.ts +9 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +42 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/codegen.d.ts +1 -0
- package/dist/commands/codegen.d.ts.map +1 -1
- package/dist/commands/codegen.js +6 -5
- package/dist/commands/codegen.js.map +1 -1
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +10 -2
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +9 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +31 -4
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +6 -15
- package/dist/commands/docs.js.map +1 -1
- package/dist/deploy/depends.d.ts.map +1 -1
- package/dist/deploy/depends.js +24 -2
- package/dist/deploy/depends.js.map +1 -1
- package/dist/deploy/download.d.ts +7 -3
- package/dist/deploy/download.d.ts.map +1 -1
- package/dist/deploy/download.js +19 -20
- package/dist/deploy/download.js.map +1 -1
- package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
- package/dist/deploy/freedesktop/copyright.js +2 -13
- package/dist/deploy/freedesktop/copyright.js.map +1 -1
- package/dist/deploy/freedesktop/metainfo.d.ts.map +1 -1
- package/dist/deploy/freedesktop/metainfo.js +41 -2
- package/dist/deploy/freedesktop/metainfo.js.map +1 -1
- package/dist/deploy/freedesktop/validate.d.ts.map +1 -1
- package/dist/deploy/freedesktop/validate.js +58 -14
- package/dist/deploy/freedesktop/validate.js.map +1 -1
- package/dist/deploy/freedesktop/xml.d.ts.map +1 -1
- package/dist/deploy/freedesktop/xml.js +21 -25
- package/dist/deploy/freedesktop/xml.js.map +1 -1
- package/dist/deploy/native-addon.d.ts +5 -0
- package/dist/deploy/native-addon.d.ts.map +1 -0
- package/dist/deploy/native-addon.js +134 -0
- package/dist/deploy/native-addon.js.map +1 -0
- package/dist/deploy/nfpm/config.d.ts +3 -2
- package/dist/deploy/nfpm/config.d.ts.map +1 -1
- package/dist/deploy/nfpm/config.js +7 -4
- package/dist/deploy/nfpm/config.js.map +1 -1
- package/dist/deploy/nfpm/contents.d.ts.map +1 -1
- package/dist/deploy/nfpm/contents.js +8 -0
- package/dist/deploy/nfpm/contents.js.map +1 -1
- package/dist/deploy/node-runtime/download.js +2 -2
- package/dist/deploy/node-runtime/download.js.map +1 -1
- package/dist/deploy/node-runtime/elf.d.ts +4 -1
- package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
- package/dist/deploy/node-runtime/elf.js +34 -5
- package/dist/deploy/node-runtime/elf.js.map +1 -1
- package/dist/deploy/node-runtime/guard.d.ts.map +1 -1
- package/dist/deploy/node-runtime/guard.js +7 -14
- package/dist/deploy/node-runtime/guard.js.map +1 -1
- package/dist/deploy/node-runtime/index.d.ts +3 -1
- package/dist/deploy/node-runtime/index.d.ts.map +1 -1
- package/dist/deploy/node-runtime/index.js +103 -10
- package/dist/deploy/node-runtime/index.js.map +1 -1
- package/dist/deploy/node-runtime/license.d.ts.map +1 -1
- package/dist/deploy/node-runtime/license.js +2 -9
- package/dist/deploy/node-runtime/license.js.map +1 -1
- package/dist/deploy/node-runtime/sonames.d.ts +5 -0
- package/dist/deploy/node-runtime/sonames.d.ts.map +1 -0
- package/dist/deploy/node-runtime/sonames.js +13 -0
- package/dist/deploy/node-runtime/sonames.js.map +1 -0
- package/dist/deploy/notices/node-runtime.js +2 -2
- package/dist/deploy/notices/node-runtime.js.map +1 -1
- package/dist/deploy/notices/packages.d.ts.map +1 -1
- package/dist/deploy/notices/packages.js +3 -10
- package/dist/deploy/notices/packages.js.map +1 -1
- package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
- package/dist/deploy/payload/copy-tree.js +3 -3
- package/dist/deploy/payload/copy-tree.js.map +1 -1
- package/dist/deploy/payload/launcher.d.ts.map +1 -1
- package/dist/deploy/payload/launcher.js +14 -1
- package/dist/deploy/payload/launcher.js.map +1 -1
- package/dist/deploy/payload/stage.d.ts +2 -1
- package/dist/deploy/payload/stage.d.ts.map +1 -1
- package/dist/deploy/payload/stage.js +47 -8
- package/dist/deploy/payload/stage.js.map +1 -1
- package/dist/deploy/run-deploy.d.ts +2 -0
- package/dist/deploy/run-deploy.d.ts.map +1 -1
- package/dist/deploy/run-deploy.js +197 -52
- package/dist/deploy/run-deploy.js.map +1 -1
- package/dist/deploy/settings/arch.d.ts +9 -3
- package/dist/deploy/settings/arch.d.ts.map +1 -1
- package/dist/deploy/settings/arch.js +27 -6
- package/dist/deploy/settings/arch.js.map +1 -1
- package/dist/deploy/settings/build-manifest.d.ts +6 -1
- package/dist/deploy/settings/build-manifest.d.ts.map +1 -1
- package/dist/deploy/settings/build-manifest.js +17 -17
- package/dist/deploy/settings/build-manifest.js.map +1 -1
- package/dist/deploy/settings/extra-files.d.ts +1 -1
- package/dist/deploy/settings/extra-files.d.ts.map +1 -1
- package/dist/deploy/settings/extra-files.js +25 -3
- package/dist/deploy/settings/extra-files.js.map +1 -1
- package/dist/deploy/settings/index.d.ts +3 -1
- package/dist/deploy/settings/index.d.ts.map +1 -1
- package/dist/deploy/settings/index.js +4 -2
- package/dist/deploy/settings/index.js.map +1 -1
- package/dist/deploy/settings/libraries.d.ts.map +1 -1
- package/dist/deploy/settings/libraries.js +3 -19
- package/dist/deploy/settings/libraries.js.map +1 -1
- package/dist/deploy/settings/paths.d.ts +4 -3
- package/dist/deploy/settings/paths.d.ts.map +1 -1
- package/dist/deploy/settings/paths.js +118 -19
- package/dist/deploy/settings/paths.js.map +1 -1
- package/dist/deploy/settings/screenshots.d.ts.map +1 -1
- package/dist/deploy/settings/screenshots.js +6 -31
- package/dist/deploy/settings/screenshots.js.map +1 -1
- package/dist/deploy/targets/appimage.d.ts.map +1 -1
- package/dist/deploy/targets/appimage.js +1 -0
- package/dist/deploy/targets/appimage.js.map +1 -1
- package/dist/deploy/targets/flatpak-pnpm.js +4 -4
- package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
- package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-source.js +17 -6
- package/dist/deploy/targets/flatpak-source.js.map +1 -1
- package/dist/deploy/targets/flatpak-sources.js +1 -1
- package/dist/deploy/targets/flatpak-sources.js.map +1 -1
- package/dist/deploy/targets/flatpak.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak.js +1 -0
- package/dist/deploy/targets/flatpak.js.map +1 -1
- package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
- package/dist/deploy/targets/nfpm-target.js +13 -7
- package/dist/deploy/targets/nfpm-target.js.map +1 -1
- package/dist/deploy/types.d.ts +5 -2
- package/dist/deploy/types.d.ts.map +1 -1
- package/dist/deploy/types.js.map +1 -1
- package/dist/deploy/vendored/appimagetool.js +2 -2
- package/dist/deploy/vendored/appimagetool.js.map +1 -1
- package/dist/deploy/vendored/nfpm.js +1 -1
- package/dist/deploy/vendored/nfpm.js.map +1 -1
- package/dist/dev/catalog-writes.d.ts +8 -0
- package/dist/dev/catalog-writes.d.ts.map +1 -0
- package/dist/dev/catalog-writes.js +26 -0
- package/dist/dev/catalog-writes.js.map +1 -0
- package/dist/dev/entry-env.d.ts +2 -1
- package/dist/dev/entry-env.d.ts.map +1 -1
- package/dist/dev/entry-env.js +2 -1
- package/dist/dev/entry-env.js.map +1 -1
- package/dist/dev/font-dir.d.ts +3 -0
- package/dist/dev/font-dir.d.ts.map +1 -0
- package/dist/dev/font-dir.js +9 -0
- package/dist/dev/font-dir.js.map +1 -0
- package/dist/dev/headless.d.ts +3 -0
- package/dist/dev/headless.d.ts.map +1 -0
- package/dist/dev/headless.js +35 -0
- package/dist/dev/headless.js.map +1 -0
- package/dist/dev/runner-deps.d.ts +1 -1
- package/dist/dev/runner-deps.d.ts.map +1 -1
- package/dist/dev/runner-deps.js +12 -7
- package/dist/dev/runner-deps.js.map +1 -1
- package/dist/dev/runner-main.d.ts.map +1 -1
- package/dist/dev/runner-main.js +11 -5
- package/dist/dev/runner-main.js.map +1 -1
- package/dist/dev/runner.d.ts +2 -0
- package/dist/dev/runner.d.ts.map +1 -1
- package/dist/dev/runner.js +26 -7
- package/dist/dev/runner.js.map +1 -1
- package/dist/dev/schema-dir.d.ts +1 -1
- package/dist/dev/schema-dir.d.ts.map +1 -1
- package/dist/dev/schema-dir.js +2 -4
- package/dist/dev/schema-dir.js.map +1 -1
- package/dist/dev/supervisor.d.ts +3 -1
- package/dist/dev/supervisor.d.ts.map +1 -1
- package/dist/dev/supervisor.js +132 -29
- package/dist/dev/supervisor.js.map +1 -1
- package/dist/dev/vite-dev-server.d.ts +1 -1
- package/dist/dev/vite-dev-server.d.ts.map +1 -1
- package/dist/dev/vite-dev-server.js +14 -2
- package/dist/dev/vite-dev-server.js.map +1 -1
- package/dist/dev/watch-ignore.d.ts +3 -0
- package/dist/dev/watch-ignore.d.ts.map +1 -0
- package/dist/dev/watch-ignore.js +11 -0
- package/dist/dev/watch-ignore.js.map +1 -0
- package/dist/i18n/catalogs.d.ts +6 -2
- package/dist/i18n/catalogs.d.ts.map +1 -1
- package/dist/i18n/catalogs.js +12 -8
- package/dist/i18n/catalogs.js.map +1 -1
- package/dist/i18n/metadata-templates.d.ts.map +1 -1
- package/dist/i18n/metadata-templates.js +2 -1
- package/dist/i18n/metadata-templates.js.map +1 -1
- package/dist/i18n/source-messages.d.ts +5 -21
- package/dist/i18n/source-messages.d.ts.map +1 -1
- package/dist/i18n/source-messages.js +1037 -92
- package/dist/i18n/source-messages.js.map +1 -1
- package/dist/i18n/types.d.ts +6 -7
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/i18n/types.js +83 -96
- package/dist/i18n/types.js.map +1 -1
- package/dist/internal/agent-rules.d.ts.map +1 -1
- package/dist/internal/agent-rules.js +7 -2
- package/dist/internal/agent-rules.js.map +1 -1
- package/dist/internal/banner.d.ts +4 -2
- package/dist/internal/banner.d.ts.map +1 -1
- package/dist/internal/banner.js +19 -5
- package/dist/internal/banner.js.map +1 -1
- package/dist/internal/build-manifest.d.ts +5 -2
- package/dist/internal/build-manifest.d.ts.map +1 -1
- package/dist/internal/build-manifest.js +15 -2
- package/dist/internal/build-manifest.js.map +1 -1
- package/dist/internal/build-output.d.ts +8 -0
- package/dist/internal/build-output.d.ts.map +1 -0
- package/dist/internal/build-output.js +116 -0
- package/dist/internal/build-output.js.map +1 -0
- package/dist/internal/cache-root.d.ts +3 -0
- package/dist/internal/cache-root.d.ts.map +1 -0
- package/dist/internal/cache-root.js +9 -0
- package/dist/internal/cache-root.js.map +1 -0
- package/dist/internal/compile-cache-store.d.ts +4 -0
- package/dist/internal/compile-cache-store.d.ts.map +1 -0
- package/dist/internal/compile-cache-store.js +41 -0
- package/dist/internal/compile-cache-store.js.map +1 -0
- package/dist/internal/compile-cache.d.ts +4 -0
- package/dist/internal/compile-cache.d.ts.map +1 -0
- package/dist/internal/compile-cache.js +16 -0
- package/dist/internal/compile-cache.js.map +1 -0
- package/dist/internal/deploy-out-dir.d.ts +4 -0
- package/dist/internal/deploy-out-dir.d.ts.map +1 -0
- package/dist/internal/deploy-out-dir.js +5 -0
- package/dist/internal/deploy-out-dir.js.map +1 -0
- package/dist/internal/docs-elements.d.ts +1 -0
- package/dist/internal/docs-elements.d.ts.map +1 -1
- package/dist/internal/docs-elements.js +6 -5
- package/dist/internal/docs-elements.js.map +1 -1
- package/dist/internal/entry-arg.d.ts +4 -1
- package/dist/internal/entry-arg.d.ts.map +1 -1
- package/dist/internal/entry-arg.js +7 -1
- package/dist/internal/entry-arg.js.map +1 -1
- package/dist/internal/errors.d.ts +2 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +1 -1
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/font-path.d.ts +4 -0
- package/dist/internal/font-path.d.ts.map +1 -0
- package/dist/internal/font-path.js +12 -0
- package/dist/internal/font-path.js.map +1 -0
- package/dist/internal/font-staging.d.ts +6 -0
- package/dist/internal/font-staging.d.ts.map +1 -0
- package/dist/internal/font-staging.js +94 -0
- package/dist/internal/font-staging.js.map +1 -0
- package/dist/internal/icon-path.d.ts.map +1 -1
- package/dist/internal/icon-path.js +5 -16
- package/dist/internal/icon-path.js.map +1 -1
- package/dist/internal/icon-staging.d.ts +3 -0
- package/dist/internal/icon-staging.d.ts.map +1 -0
- package/dist/internal/icon-staging.js +40 -0
- package/dist/internal/icon-staging.js.map +1 -0
- package/dist/internal/import-scan.d.ts +4 -0
- package/dist/internal/import-scan.d.ts.map +1 -0
- package/dist/internal/import-scan.js +138 -0
- package/dist/internal/import-scan.js.map +1 -0
- package/dist/internal/module-hash.d.ts +3 -0
- package/dist/internal/module-hash.d.ts.map +1 -0
- package/dist/internal/module-hash.js +19 -0
- package/dist/internal/module-hash.js.map +1 -0
- package/dist/internal/output-directory.d.ts +15 -0
- package/dist/internal/output-directory.d.ts.map +1 -0
- package/dist/internal/output-directory.js +490 -0
- package/dist/internal/output-directory.js.map +1 -0
- package/dist/internal/parent-process.d.ts +12 -0
- package/dist/internal/parent-process.d.ts.map +1 -0
- package/dist/internal/parent-process.js +74 -0
- package/dist/internal/parent-process.js.map +1 -0
- package/dist/internal/prepare-project.d.ts +3 -0
- package/dist/internal/prepare-project.d.ts.map +1 -1
- package/dist/internal/prepare-project.js +9 -3
- package/dist/internal/prepare-project.js.map +1 -1
- package/dist/internal/react-compiler-cache.d.ts +13 -0
- package/dist/internal/react-compiler-cache.d.ts.map +1 -0
- package/dist/internal/react-compiler-cache.js +182 -0
- package/dist/internal/react-compiler-cache.js.map +1 -0
- package/dist/internal/source-imports.d.ts +7 -2
- package/dist/internal/source-imports.d.ts.map +1 -1
- package/dist/internal/source-imports.js +38 -49
- package/dist/internal/source-imports.js.map +1 -1
- package/dist/internal/staging-file.d.ts +5 -0
- package/dist/internal/staging-file.d.ts.map +1 -0
- package/dist/internal/staging-file.js +35 -0
- package/dist/internal/staging-file.js.map +1 -0
- package/dist/internal/xdg-banner.d.ts +4 -0
- package/dist/internal/xdg-banner.d.ts.map +1 -0
- package/dist/internal/xdg-banner.js +11 -0
- package/dist/internal/xdg-banner.js.map +1 -0
- package/dist/internal/xdg-data-dirs.d.ts +2 -1
- package/dist/internal/xdg-data-dirs.d.ts.map +1 -1
- package/dist/internal/xdg-data-dirs.js +1 -1
- package/dist/internal/xdg-data-dirs.js.map +1 -1
- package/dist/mcp/client.js +2 -2
- package/dist/mcp/client.js.map +1 -1
- package/dist/settings/render.d.ts +0 -1
- package/dist/settings/render.d.ts.map +1 -1
- package/dist/settings/render.js +1 -11
- package/dist/settings/render.js.map +1 -1
- package/dist/settings/schema.d.ts +2 -2
- package/dist/settings/schema.d.ts.map +1 -1
- package/dist/settings/schema.js +24 -70
- package/dist/settings/schema.js.map +1 -1
- package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
- package/dist/vite-plugins/asset-extensions.js +2 -0
- package/dist/vite-plugins/asset-extensions.js.map +1 -1
- package/dist/vite-plugins/asset-imports.d.ts +1 -2
- package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
- package/dist/vite-plugins/asset-imports.js +36 -35
- package/dist/vite-plugins/asset-imports.js.map +1 -1
- package/dist/vite-plugins/asset-specifier.d.ts +5 -3
- package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
- package/dist/vite-plugins/asset-specifier.js +10 -4
- package/dist/vite-plugins/asset-specifier.js.map +1 -1
- package/dist/vite-plugins/build-manifest.d.ts +5 -1
- package/dist/vite-plugins/build-manifest.d.ts.map +1 -1
- package/dist/vite-plugins/build-manifest.js +2 -1
- package/dist/vite-plugins/build-manifest.js.map +1 -1
- package/dist/vite-plugins/built-url.d.ts.map +1 -1
- package/dist/vite-plugins/built-url.js +5 -2
- package/dist/vite-plugins/built-url.js.map +1 -1
- package/dist/vite-plugins/css.d.ts.map +1 -1
- package/dist/vite-plugins/css.js +13 -5
- package/dist/vite-plugins/css.js.map +1 -1
- package/dist/vite-plugins/ensure-store.d.ts +1 -1
- package/dist/vite-plugins/ensure-store.d.ts.map +1 -1
- package/dist/vite-plugins/ensure-store.js +2 -2
- package/dist/vite-plugins/ensure-store.js.map +1 -1
- package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts +6 -1
- package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts.map +1 -1
- package/dist/vite-plugins/fast-refresh/refresh-filter.js +3 -1
- package/dist/vite-plugins/fast-refresh/refresh-filter.js.map +1 -1
- package/dist/vite-plugins/fast-refresh/swc-refresh.d.ts.map +1 -1
- package/dist/vite-plugins/fast-refresh/swc-refresh.js +23 -14
- package/dist/vite-plugins/fast-refresh/swc-refresh.js.map +1 -1
- package/dist/vite-plugins/font-name.d.ts +3 -0
- package/dist/vite-plugins/font-name.d.ts.map +1 -0
- package/dist/vite-plugins/font-name.js +406 -0
- package/dist/vite-plugins/font-name.js.map +1 -0
- package/dist/vite-plugins/font-worker-env.d.ts +4 -0
- package/dist/vite-plugins/font-worker-env.d.ts.map +1 -0
- package/dist/vite-plugins/font-worker-env.js +19 -0
- package/dist/vite-plugins/font-worker-env.js.map +1 -0
- package/dist/vite-plugins/font.d.ts +4 -0
- package/dist/vite-plugins/font.d.ts.map +1 -0
- package/dist/vite-plugins/font.js +111 -0
- package/dist/vite-plugins/font.js.map +1 -0
- package/dist/vite-plugins/i18n.d.ts +11 -2
- package/dist/vite-plugins/i18n.d.ts.map +1 -1
- package/dist/vite-plugins/i18n.js +75 -8
- package/dist/vite-plugins/i18n.js.map +1 -1
- package/dist/vite-plugins/icon-worker-env.d.ts +4 -0
- package/dist/vite-plugins/icon-worker-env.d.ts.map +1 -0
- package/dist/vite-plugins/icon-worker-env.js +23 -0
- package/dist/vite-plugins/icon-worker-env.js.map +1 -0
- package/dist/vite-plugins/icons.d.ts.map +1 -1
- package/dist/vite-plugins/icons.js +4 -9
- package/dist/vite-plugins/icons.js.map +1 -1
- package/dist/vite-plugins/index.d.ts +10 -1
- package/dist/vite-plugins/index.d.ts.map +1 -1
- package/dist/vite-plugins/index.js +19 -7
- package/dist/vite-plugins/index.js.map +1 -1
- package/dist/vite-plugins/native.d.ts.map +1 -1
- package/dist/vite-plugins/native.js +0 -4
- package/dist/vite-plugins/native.js.map +1 -1
- package/dist/vite-plugins/react-compiler.d.ts.map +1 -1
- package/dist/vite-plugins/react-compiler.js +57 -18
- package/dist/vite-plugins/react-compiler.js.map +1 -1
- package/dist/vite-plugins/resource-shared.d.ts +3 -1
- package/dist/vite-plugins/resource-shared.d.ts.map +1 -1
- package/dist/vite-plugins/resource-shared.js +3 -1
- package/dist/vite-plugins/resource-shared.js.map +1 -1
- package/dist/vite-plugins/resources.d.ts.map +1 -1
- package/dist/vite-plugins/resources.js +40 -102
- package/dist/vite-plugins/resources.js.map +1 -1
- package/dist/vite-plugins/self-contained.d.ts.map +1 -1
- package/dist/vite-plugins/self-contained.js +159 -158
- package/dist/vite-plugins/self-contained.js.map +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
- package/dist/vite-plugins/settings-worker-env.js +5 -7
- package/dist/vite-plugins/settings-worker-env.js.map +1 -1
- package/dist/vite-plugins/settings.d.ts +1 -2
- package/dist/vite-plugins/settings.d.ts.map +1 -1
- package/dist/vite-plugins/settings.js +32 -27
- package/dist/vite-plugins/settings.js.map +1 -1
- package/dist/vite-plugins/store-links.d.ts.map +1 -1
- package/dist/vite-plugins/store-links.js +9 -5
- package/dist/vite-plugins/store-links.js.map +1 -1
- package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
- package/dist/vite-plugins/undeclared-library.js +25 -25
- package/dist/vite-plugins/undeclared-library.js.map +1 -1
- package/dist/vite-plugins/worker.d.ts.map +1 -1
- package/dist/vite-plugins/worker.js +21 -14
- package/dist/vite-plugins/worker.js.map +1 -1
- package/dist/vitest-plugin.d.ts +4 -3
- package/dist/vitest-plugin.d.ts.map +1 -1
- package/dist/vitest-plugin.js +12 -8
- package/dist/vitest-plugin.js.map +1 -1
- package/env.d.ts +9 -0
- package/package.json +28 -17
- package/src/builder.ts +45 -12
- package/src/cli.ts +15 -2
- package/src/codegen/config-dependencies.ts +470 -0
- package/src/codegen/freshness.ts +70 -17
- package/src/codegen/reference.ts +2 -8
- package/src/codegen/report.ts +5 -21
- package/src/codegen/run-codegen.ts +73 -160
- package/src/codegen/store-resolver.ts +16 -27
- package/src/commands/build.ts +13 -2
- package/src/commands/cleanup.ts +48 -0
- package/src/commands/codegen.ts +6 -5
- package/src/commands/deploy.ts +10 -2
- package/src/commands/dev.ts +45 -4
- package/src/commands/docs.ts +6 -17
- package/src/deploy/depends.ts +30 -3
- package/src/deploy/download.ts +33 -24
- package/src/deploy/freedesktop/copyright.ts +2 -14
- package/src/deploy/freedesktop/metainfo.ts +73 -21
- package/src/deploy/freedesktop/validate.ts +80 -15
- package/src/deploy/freedesktop/xml.ts +24 -32
- package/src/deploy/native-addon.ts +183 -0
- package/src/deploy/nfpm/config.ts +13 -4
- package/src/deploy/nfpm/contents.ts +8 -0
- package/src/deploy/node-runtime/download.ts +2 -2
- package/src/deploy/node-runtime/elf.ts +46 -5
- package/src/deploy/node-runtime/guard.ts +10 -14
- package/src/deploy/node-runtime/index.ts +148 -12
- package/src/deploy/node-runtime/license.ts +2 -8
- package/src/deploy/node-runtime/sonames.ts +17 -0
- package/src/deploy/notices/node-runtime.ts +2 -2
- package/src/deploy/notices/packages.ts +3 -11
- package/src/deploy/payload/copy-tree.ts +3 -4
- package/src/deploy/payload/launcher.ts +20 -1
- package/src/deploy/payload/stage.ts +78 -11
- package/src/deploy/run-deploy.ts +227 -72
- package/src/deploy/settings/arch.ts +40 -8
- package/src/deploy/settings/build-manifest.ts +30 -17
- package/src/deploy/settings/extra-files.ts +30 -4
- package/src/deploy/settings/index.ts +7 -2
- package/src/deploy/settings/libraries.ts +2 -22
- package/src/deploy/settings/paths.ts +78 -21
- package/src/deploy/settings/screenshots.ts +8 -46
- package/src/deploy/targets/appimage.ts +1 -0
- package/src/deploy/targets/flatpak-pnpm.ts +4 -4
- package/src/deploy/targets/flatpak-source.ts +19 -6
- package/src/deploy/targets/flatpak-sources.ts +1 -1
- package/src/deploy/targets/flatpak.ts +1 -0
- package/src/deploy/targets/nfpm-target.ts +14 -6
- package/src/deploy/types.ts +5 -1
- package/src/deploy/vendored/appimagetool.ts +2 -2
- package/src/deploy/vendored/nfpm.ts +1 -1
- package/src/dev/catalog-writes.ts +35 -0
- package/src/dev/entry-env.ts +2 -1
- package/src/dev/font-dir.ts +11 -0
- package/src/dev/headless.ts +40 -0
- package/src/dev/runner-deps.ts +20 -7
- package/src/dev/runner-main.ts +13 -5
- package/src/dev/runner.ts +37 -7
- package/src/dev/schema-dir.ts +2 -4
- package/src/dev/supervisor.ts +183 -32
- package/src/dev/vite-dev-server.ts +20 -2
- package/src/dev/watch-ignore.ts +16 -0
- package/src/i18n/catalogs.ts +23 -9
- package/src/i18n/metadata-templates.ts +2 -1
- package/src/i18n/source-messages.ts +1641 -262
- package/src/i18n/types.ts +97 -131
- package/src/internal/agent-rules.ts +7 -2
- package/src/internal/banner.ts +27 -6
- package/src/internal/build-manifest.ts +25 -1
- package/src/internal/build-output.ts +171 -0
- package/src/internal/cache-root.ts +12 -0
- package/src/internal/compile-cache-store.ts +50 -0
- package/src/internal/compile-cache.ts +20 -0
- package/src/internal/deploy-out-dir.ts +8 -0
- package/src/internal/docs-elements.ts +11 -5
- package/src/internal/entry-arg.ts +8 -1
- package/src/internal/errors.ts +1 -1
- package/src/internal/font-path.ts +15 -0
- package/src/internal/font-staging.ts +131 -0
- package/src/internal/icon-path.ts +6 -19
- package/src/internal/icon-staging.ts +58 -0
- package/src/internal/import-scan.ts +206 -0
- package/src/internal/module-hash.ts +22 -0
- package/src/internal/output-directory.ts +699 -0
- package/src/internal/parent-process.ts +110 -0
- package/src/internal/prepare-project.ts +11 -5
- package/src/internal/react-compiler-cache.ts +246 -0
- package/src/internal/source-imports.ts +64 -55
- package/src/internal/staging-file.ts +42 -0
- package/src/internal/xdg-banner.ts +13 -0
- package/src/internal/xdg-data-dirs.ts +1 -1
- package/src/mcp/client.ts +2 -2
- package/src/settings/render.ts +1 -13
- package/src/settings/schema.ts +23 -91
- package/src/vite-plugins/asset-extensions.ts +2 -0
- package/src/vite-plugins/asset-imports.ts +39 -45
- package/src/vite-plugins/asset-specifier.ts +17 -5
- package/src/vite-plugins/build-manifest.ts +7 -1
- package/src/vite-plugins/built-url.ts +6 -2
- package/src/vite-plugins/css.ts +16 -5
- package/src/vite-plugins/ensure-store.ts +2 -2
- package/src/vite-plugins/fast-refresh/refresh-filter.ts +10 -1
- package/src/vite-plugins/fast-refresh/swc-refresh.ts +33 -14
- package/src/vite-plugins/font-name.ts +582 -0
- package/src/vite-plugins/font-worker-env.ts +25 -0
- package/src/vite-plugins/font.ts +180 -0
- package/src/vite-plugins/i18n.ts +123 -11
- package/src/vite-plugins/icon-worker-env.ts +36 -0
- package/src/vite-plugins/icons.ts +4 -10
- package/src/vite-plugins/index.ts +36 -13
- package/src/vite-plugins/native.ts +0 -5
- package/src/vite-plugins/react-compiler.ts +83 -19
- package/src/vite-plugins/resource-shared.ts +4 -0
- package/src/vite-plugins/resources.ts +47 -144
- package/src/vite-plugins/self-contained.ts +184 -185
- package/src/vite-plugins/settings-worker-env.ts +5 -7
- package/src/vite-plugins/settings.ts +44 -33
- package/src/vite-plugins/store-links.ts +10 -5
- package/src/vite-plugins/undeclared-library.ts +30 -32
- package/src/vite-plugins/worker.ts +31 -16
- package/src/vitest-plugin.ts +12 -8
- package/dist/i18n/binding-extractor.d.ts +0 -30
- package/dist/i18n/binding-extractor.d.ts.map +0 -1
- package/dist/i18n/binding-extractor.js +0 -1324
- package/dist/i18n/binding-extractor.js.map +0 -1
- package/dist/i18n/extractor-plugin.d.ts +0 -8
- package/dist/i18n/extractor-plugin.d.ts.map +0 -1
- package/dist/i18n/extractor-plugin.js +0 -247
- package/dist/i18n/extractor-plugin.js.map +0 -1
- package/dist/internal/data-dir.d.ts +0 -4
- package/dist/internal/data-dir.d.ts.map +0 -1
- package/dist/internal/data-dir.js +0 -52
- package/dist/internal/data-dir.js.map +0 -1
- package/dist/vite-plugins/animated.d.ts +0 -5
- package/dist/vite-plugins/animated.d.ts.map +0 -1
- package/dist/vite-plugins/animated.js +0 -325
- package/dist/vite-plugins/animated.js.map +0 -1
- package/src/i18n/binding-extractor.ts +0 -1969
- package/src/i18n/extractor-plugin.ts +0 -413
- package/src/internal/data-dir.ts +0 -70
- package/src/vite-plugins/animated.ts +0 -486
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { ExtractedKey,
|
|
2
|
-
import {
|
|
1
|
+
import type { ExtractedKey, ExtractedKeysMap, Logger, Plugin } from "i18next-cli";
|
|
2
|
+
import type { ESTree } from "vite";
|
|
3
|
+
import { type NodePath, parseSync as parseBabelSync, type Scope, traverse, types } from "@babel/core";
|
|
4
|
+
import { isPathInside, isPathWithin, toPosixPath } from "@gtkx/utils";
|
|
5
|
+
import { runExtractor } from "i18next-cli";
|
|
3
6
|
import {
|
|
4
7
|
existsSync,
|
|
5
8
|
mkdirSync,
|
|
@@ -8,339 +11,1750 @@ import {
|
|
|
8
11
|
rmSync,
|
|
9
12
|
writeFileSync,
|
|
10
13
|
} from "node:fs";
|
|
11
|
-
import { dirname, isAbsolute, join, relative, resolve
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
15
|
+
import { parseSync, Visitor } from "vite";
|
|
12
16
|
import type { CatalogProject } from "./catalogs.js";
|
|
13
17
|
import { runCliTool } from "../internal/run-cli-tool.js";
|
|
18
|
+
import { sourceLanguage } from "../internal/source-imports.js";
|
|
14
19
|
import { replaceCatalogTemplate } from "./catalog-template.js";
|
|
15
|
-
import { gtkxExtractorPlugin } from "./extractor-plugin.js";
|
|
16
20
|
import { metadataTemplateFiles } from "./metadata-templates.js";
|
|
21
|
+
import { clearI18nResources, i18nToolkitConfig } from "./types.js";
|
|
17
22
|
|
|
18
23
|
type SourceLocation = {
|
|
19
24
|
file: string;
|
|
20
25
|
line?: number | undefined;
|
|
21
26
|
column?: number | undefined;
|
|
22
27
|
};
|
|
28
|
+
type ExtractedLocation = NonNullable<ExtractedKey["locations"]>[number];
|
|
23
29
|
|
|
24
30
|
type SourceMessage = {
|
|
25
31
|
context: string | null;
|
|
26
|
-
kind: "default" | "plural" | "pluralDefaults" | "point";
|
|
27
32
|
locations: SourceLocation[];
|
|
28
|
-
msgid: string;
|
|
29
33
|
namespace: string;
|
|
30
34
|
plural: string | null;
|
|
31
35
|
singular: string;
|
|
36
|
+
sourceKey: string;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
|
-
type
|
|
39
|
+
type SyntheticEntry = {
|
|
40
|
+
call: string;
|
|
41
|
+
line: number | undefined;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type SourceExtraction = {
|
|
45
|
+
messages: SourceMessage[];
|
|
46
|
+
output: string;
|
|
47
|
+
project: CatalogProject;
|
|
48
|
+
workDir: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type CatalogTemplateExtraction = {
|
|
35
52
|
messages: SourceMessage[];
|
|
36
|
-
|
|
53
|
+
output?: string | undefined;
|
|
54
|
+
project: CatalogProject;
|
|
55
|
+
shouldPreserveMetadataMessages: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type PluralVariant = {
|
|
59
|
+
category: string;
|
|
60
|
+
defaultValue: string;
|
|
61
|
+
locations: SourceLocation[];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type PluralGroup = {
|
|
65
|
+
baseKey: string;
|
|
66
|
+
namespace: string;
|
|
67
|
+
variants: PluralVariant[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type TranslationReferences = {
|
|
71
|
+
calls: Set<number>;
|
|
72
|
+
elements: Set<number>;
|
|
73
|
+
entryLocations: Map<string, SourcePoint[]>;
|
|
74
|
+
locations: Map<string, SourcePoint>;
|
|
75
|
+
masks: SourceMask[];
|
|
76
|
+
sourcePoints: Map<number, SourcePoint>;
|
|
77
|
+
};
|
|
78
|
+
type SourceMask = { end: number; start: number };
|
|
79
|
+
type SourcePoint = { column: number; line: number };
|
|
80
|
+
type TranslationCallKind = "alias" | "canonical";
|
|
81
|
+
type ImportedBinding = {
|
|
82
|
+
imported: string;
|
|
83
|
+
local: string;
|
|
84
|
+
source: string;
|
|
85
|
+
style: "default" | "named" | "namespace";
|
|
86
|
+
};
|
|
87
|
+
type TranslationHookBinding = { call: types.CallExpression; isCanonical: boolean };
|
|
88
|
+
type TranslationMemberExpression = types.MemberExpression | types.OptionalMemberExpression;
|
|
89
|
+
type ResolvedBinding = NonNullable<ReturnType<Scope["getBinding"]>>;
|
|
90
|
+
type LocatableNode = { loc?: { start: SourcePoint } | null | undefined };
|
|
91
|
+
|
|
92
|
+
const CONTEXT_SEPARATOR = "\u{4}";
|
|
93
|
+
const DEFAULT_NAMESPACE = "translation";
|
|
94
|
+
const POTFILES_FILENAME = "POTFILES.in";
|
|
95
|
+
const SYNTHETIC_FILENAME = "messages.js";
|
|
96
|
+
const UNSUPPORTED_PLURAL_OPTION = /^defaultValue_(few|many|two|zero)$/u;
|
|
97
|
+
const STATIC_SOURCE_OPTION_NAMES: ReadonlySet<string> = new Set([
|
|
98
|
+
"context",
|
|
99
|
+
"defaultValue",
|
|
100
|
+
"defaultValue_one",
|
|
101
|
+
"defaultValue_other",
|
|
102
|
+
"keyPrefix",
|
|
103
|
+
]);
|
|
104
|
+
const NESTED_CONTEXT = /["']?context["']?\s*:\s*(?:"([^"]*)"|'([^']*)')/u;
|
|
105
|
+
const NESTED_COUNT = /["']?count["']?\s*:/u;
|
|
106
|
+
const NESTED_STRUCTURE_TOKEN = /"[^"]*"|'[^']*'|[()]/gu;
|
|
107
|
+
const TRANSLATION_MODULES = new Set(["@gtkx/i18n", "i18next"]);
|
|
108
|
+
const TRANS_ELEMENTS: ReadonlySet<string> = new Set(["Trans", "TransWithoutContext"]);
|
|
109
|
+
const STATIC_KEY_WRAPPER_TYPES: ReadonlySet<string> = new Set([
|
|
110
|
+
"ParenthesizedExpression",
|
|
111
|
+
"TSAsExpression",
|
|
112
|
+
"TSSatisfiesExpression",
|
|
113
|
+
"TSTypeAssertion",
|
|
114
|
+
"TSNonNullExpression",
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
class SourceExtractionError extends Error {}
|
|
118
|
+
|
|
119
|
+
const sourceErrorMessage = (error: unknown): string => {
|
|
120
|
+
if (!(error instanceof Error)) {
|
|
121
|
+
return String(error);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
"code" in error &&
|
|
126
|
+
error.code === "BABEL_PARSE_ERROR"
|
|
127
|
+
) {
|
|
128
|
+
const separator = error.message.indexOf(": ");
|
|
129
|
+
|
|
130
|
+
if (separator !== -1) {
|
|
131
|
+
return error.message.slice(separator + 2);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return error.message;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const projectSourcePath = (root: string, path: string): string => {
|
|
139
|
+
const absolute = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
140
|
+
|
|
141
|
+
return toPosixPath(isPathInside(root, absolute) ? relative(root, absolute) : path);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const sourceExtractionError = (
|
|
145
|
+
root: string,
|
|
146
|
+
path: string,
|
|
147
|
+
point: SourcePoint | null,
|
|
148
|
+
error: unknown,
|
|
149
|
+
): SourceExtractionError => {
|
|
150
|
+
if (error instanceof SourceExtractionError) {
|
|
151
|
+
return error;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const file = projectSourcePath(root, path);
|
|
155
|
+
const location = point === null
|
|
156
|
+
? file
|
|
157
|
+
: `${file}:${String(point.line)}:${String(point.column + 1)}`;
|
|
158
|
+
|
|
159
|
+
return new SourceExtractionError(`${location}: ${sourceErrorMessage(error)}`, { cause: error });
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const locatablePoint = (node: LocatableNode): SourcePoint | null => {
|
|
163
|
+
const location = node.loc?.start;
|
|
164
|
+
|
|
165
|
+
return location === undefined ? null : { column: location.column, line: location.line };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const isSourcePoint = (value: unknown): value is SourcePoint =>
|
|
169
|
+
typeof value === "object" &&
|
|
170
|
+
value !== null &&
|
|
171
|
+
"column" in value &&
|
|
172
|
+
typeof value.column === "number" &&
|
|
173
|
+
"line" in value &&
|
|
174
|
+
typeof value.line === "number";
|
|
175
|
+
|
|
176
|
+
const errorSourcePoint = (error: unknown): SourcePoint | null => {
|
|
177
|
+
if (typeof error !== "object" || error === null) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ("loc" in error && isSourcePoint(error.loc)) {
|
|
182
|
+
return error.loc;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return "location" in error && isSourcePoint(error.location) ? error.location : null;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const atSourceNode = <T>(
|
|
189
|
+
root: string,
|
|
190
|
+
path: string,
|
|
191
|
+
node: LocatableNode,
|
|
192
|
+
operation: () => T,
|
|
193
|
+
): T => {
|
|
194
|
+
try {
|
|
195
|
+
return operation();
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw sourceExtractionError(root, path, locatablePoint(node), error);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const atExtractedLocation = <T>(
|
|
202
|
+
root: string,
|
|
203
|
+
locations: SourceLocation[],
|
|
204
|
+
operation: () => T,
|
|
205
|
+
): T => {
|
|
206
|
+
const location = locations.find((candidate) =>
|
|
207
|
+
candidate.line !== undefined && candidate.column !== undefined);
|
|
208
|
+
|
|
209
|
+
if (location?.line === undefined || location.column === undefined) {
|
|
210
|
+
return operation();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
return operation();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw sourceExtractionError(
|
|
217
|
+
root,
|
|
218
|
+
location.file,
|
|
219
|
+
{ column: location.column, line: location.line },
|
|
220
|
+
error,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const quietLogger = (): { logger: Logger; reports: string[] } => {
|
|
226
|
+
const reports: string[] = [];
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
reports,
|
|
230
|
+
logger: {
|
|
231
|
+
info() {
|
|
232
|
+
return;
|
|
233
|
+
},
|
|
234
|
+
warn(message, more) {
|
|
235
|
+
const detail = more === undefined ? "" : ` ${String(more)}`;
|
|
236
|
+
reports.push(`${message}${detail}`);
|
|
237
|
+
},
|
|
238
|
+
error(message) {
|
|
239
|
+
reports.push(String(message));
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const splitContext = (key: string): { context: string | null; msgid: string } => {
|
|
246
|
+
const index = key.lastIndexOf(CONTEXT_SEPARATOR);
|
|
247
|
+
|
|
248
|
+
if (index === -1) {
|
|
249
|
+
return { context: null, msgid: key };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { context: key.slice(index + CONTEXT_SEPARATOR.length), msgid: key.slice(0, index) };
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const namespaceFor = (entry: ExtractedKey): string =>
|
|
256
|
+
typeof entry.ns === "string" ? entry.ns : DEFAULT_NAMESPACE;
|
|
257
|
+
|
|
258
|
+
const validateIdentity = (context: string | null, msgid: string): void => {
|
|
259
|
+
if (context === "" || msgid.length === 0) {
|
|
260
|
+
throw new Error("Translation keys and contexts cannot be empty");
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const compareLocations = (left: SourceLocation, right: SourceLocation): number =>
|
|
265
|
+
left.file.localeCompare(right.file) ||
|
|
266
|
+
(left.line ?? 0) - (right.line ?? 0) ||
|
|
267
|
+
(left.column ?? 0) - (right.column ?? 0);
|
|
268
|
+
|
|
269
|
+
const normalizedLocations = (locations: SourceLocation[]): SourceLocation[] => {
|
|
270
|
+
const unique: Map<string, SourceLocation> = new Map();
|
|
271
|
+
|
|
272
|
+
for (const location of locations) {
|
|
273
|
+
const key = `${location.file}\0${String(location.line)}\0${String(location.column)}`;
|
|
274
|
+
unique.set(key, location);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return unique.values().toArray().toSorted(compareLocations);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const pointSource = (entry: ExtractedKey, msgid: string): string => {
|
|
281
|
+
if (typeof entry.defaultValue !== "string") {
|
|
282
|
+
return msgid;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (entry.explicitDefault === true) {
|
|
286
|
+
return entry.defaultValue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return entry.defaultValue === msgid || msgid.endsWith(`.${entry.defaultValue}`)
|
|
290
|
+
? msgid
|
|
291
|
+
: entry.defaultValue;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const pointMessage = (entry: ExtractedKey): SourceMessage => {
|
|
295
|
+
const { context, msgid } = splitContext(entry.key);
|
|
296
|
+
validateIdentity(context, msgid);
|
|
297
|
+
const singular = pointSource(entry, msgid);
|
|
298
|
+
|
|
299
|
+
if (singular.length === 0) {
|
|
300
|
+
throw new Error("Translation source strings cannot be empty");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
context,
|
|
305
|
+
locations: normalizedLocations(entry.locations ?? []),
|
|
306
|
+
namespace: namespaceFor(entry),
|
|
307
|
+
plural: null,
|
|
308
|
+
singular,
|
|
309
|
+
sourceKey: msgid,
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const expandedPlural = (entry: ExtractedKey): { baseKey: string; variant: PluralVariant } => {
|
|
314
|
+
if (entry.isOrdinal === true) {
|
|
315
|
+
throw new Error("GNU gettext catalogs do not support i18next ordinal plurals");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const suffix = /_(few|many|one|other|two|zero)$/u.exec(entry.key);
|
|
319
|
+
const defaultValue = entry.defaultValue;
|
|
320
|
+
const category = suffix?.[1];
|
|
321
|
+
|
|
322
|
+
if (suffix === null || category === undefined || typeof defaultValue !== "string") {
|
|
323
|
+
throw new Error("Unable to recover an i18next plural source pair");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
baseKey: entry.key.slice(0, -suffix[0].length),
|
|
328
|
+
variant: {
|
|
329
|
+
category,
|
|
330
|
+
defaultValue,
|
|
331
|
+
locations: entry.locations ?? [],
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const groupedPlurals = (root: string, entries: ExtractedKey[]): PluralGroup[] => {
|
|
337
|
+
const groups: Map<string, PluralGroup> = new Map();
|
|
338
|
+
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
const { baseKey, variant } = atExtractedLocation(
|
|
341
|
+
root,
|
|
342
|
+
entry.locations ?? [],
|
|
343
|
+
() => expandedPlural(entry),
|
|
344
|
+
);
|
|
345
|
+
const namespace = namespaceFor(entry);
|
|
346
|
+
const key = `${namespace}\0${baseKey}`;
|
|
347
|
+
const group = groups.get(key) ?? { baseKey, namespace, variants: [] };
|
|
348
|
+
group.variants.push(variant);
|
|
349
|
+
groups.set(key, group);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return groups.values().toArray();
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const pairedVariants = (variants: PluralVariant[]): { one: PluralVariant; other: PluralVariant } => {
|
|
356
|
+
const categories = new Map(variants.map((variant) => [variant.category, variant]));
|
|
357
|
+
|
|
358
|
+
if (categories.size !== 2 || !categories.has("one") || !categories.has("other")) {
|
|
359
|
+
throw new Error("GNU gettext requires one singular and one plural source string");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const one = categories.get("one");
|
|
363
|
+
const other = categories.get("other");
|
|
364
|
+
|
|
365
|
+
if (one === undefined || other === undefined) {
|
|
366
|
+
throw new Error("Unable to recover an i18next plural source pair");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return { one, other };
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const pluralMessage = ({ baseKey, namespace, variants }: PluralGroup): SourceMessage => {
|
|
373
|
+
const { one, other } = pairedVariants(variants);
|
|
374
|
+
const { context, msgid } = splitContext(baseKey);
|
|
375
|
+
validateIdentity(context, msgid);
|
|
376
|
+
const singular = one.defaultValue;
|
|
377
|
+
const plural = other.defaultValue;
|
|
378
|
+
|
|
379
|
+
if (singular === plural || singular.length === 0 || plural.length === 0) {
|
|
380
|
+
throw new Error("GNU gettext plural source strings must be distinct and non-empty");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
context,
|
|
385
|
+
locations: normalizedLocations(variants.flatMap((variant) => variant.locations)),
|
|
386
|
+
namespace,
|
|
387
|
+
plural,
|
|
388
|
+
singular,
|
|
389
|
+
sourceKey: msgid,
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const pluralMessages = (root: string, entries: ExtractedKey[]): SourceMessage[] =>
|
|
394
|
+
groupedPlurals(root, entries).map((group) =>
|
|
395
|
+
atExtractedLocation(
|
|
396
|
+
root,
|
|
397
|
+
group.variants.flatMap((variant) => variant.locations),
|
|
398
|
+
() => pluralMessage(group),
|
|
399
|
+
));
|
|
400
|
+
|
|
401
|
+
const assertCardinal = (entry: ExtractedKey): void => {
|
|
402
|
+
if (entry.isOrdinal === true) {
|
|
403
|
+
throw new Error("GNU gettext catalogs do not support i18next ordinal plurals");
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const sourceMessages = (root: string, entries: ExtractedKey[]): SourceMessage[] => {
|
|
408
|
+
const points: ExtractedKey[] = [];
|
|
409
|
+
const plurals: ExtractedKey[] = [];
|
|
410
|
+
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
atExtractedLocation(root, entry.locations ?? [], () => {
|
|
413
|
+
assertCardinal(entry);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (entry.isExpandedPlural === true) {
|
|
417
|
+
plurals.push(entry);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (entry.hasCount === true) {
|
|
422
|
+
throw new Error("Count-based translations require an explicit plural source string");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
points.push(entry);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return [
|
|
429
|
+
...points.map((entry) =>
|
|
430
|
+
atExtractedLocation(root, entry.locations ?? [], () => pointMessage(entry))),
|
|
431
|
+
...pluralMessages(root, plurals),
|
|
432
|
+
];
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const compareMessages = (left: SourceMessage, right: SourceMessage): number =>
|
|
436
|
+
left.namespace.localeCompare(right.namespace) ||
|
|
437
|
+
(left.context ?? "").localeCompare(right.context ?? "") ||
|
|
438
|
+
left.singular.localeCompare(right.singular) ||
|
|
439
|
+
(left.plural ?? "").localeCompare(right.plural ?? "");
|
|
440
|
+
|
|
441
|
+
const inferLocations = (message: SourceMessage, sources: Map<string, string>): SourceMessage => {
|
|
442
|
+
if (message.locations.length > 0) {
|
|
443
|
+
return message;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const locations: SourceLocation[] = [];
|
|
447
|
+
|
|
448
|
+
for (const [file, source] of sources) {
|
|
449
|
+
const index = source.indexOf(message.sourceKey);
|
|
450
|
+
|
|
451
|
+
if (index !== -1) {
|
|
452
|
+
locations.push({ file, line: source.slice(0, index).split("\n").length });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return { ...message, locations };
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const normalizedSourceFiles = (root: string, paths: string[]): string[] => {
|
|
460
|
+
const files = paths
|
|
461
|
+
.map((path) => resolve(root, path))
|
|
462
|
+
.filter((path) => isPathWithin(root, path));
|
|
463
|
+
|
|
464
|
+
return new Set(files).values().toArray().toSorted((left, right) => left.localeCompare(right));
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
type StaticKeyWrapper = ESTree.ParenthesizedExpression |
|
|
468
|
+
ESTree.TSAsExpression |
|
|
469
|
+
ESTree.TSSatisfiesExpression |
|
|
470
|
+
ESTree.TSTypeAssertion |
|
|
471
|
+
ESTree.TSNonNullExpression;
|
|
472
|
+
|
|
473
|
+
const isStaticKeyWrapper = (key: ESTree.Node): key is StaticKeyWrapper =>
|
|
474
|
+
STATIC_KEY_WRAPPER_TYPES.has(key.type);
|
|
475
|
+
|
|
476
|
+
const unwrapStaticWrapper = (node: ESTree.Node): ESTree.Node =>
|
|
477
|
+
isStaticKeyWrapper(node) ? unwrapStaticWrapper(node.expression) : node;
|
|
478
|
+
|
|
479
|
+
const isStaticTranslationKey = (key: ESTree.Node | null | undefined): boolean => {
|
|
480
|
+
if (key === undefined || key === null) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (key.type === "Literal") {
|
|
485
|
+
return typeof key.value === "string";
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (key.type === "TemplateLiteral") {
|
|
489
|
+
return key.expressions.length === 0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (key.type === "ParenthesizedExpression") {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (isStaticKeyWrapper(key)) {
|
|
497
|
+
return isStaticTranslationKey(key.expression);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return false;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const assertStaticTranslationKey = (key: ESTree.Node | null | undefined): void => {
|
|
504
|
+
if (!isStaticTranslationKey(key)) {
|
|
505
|
+
throw new Error("Translation keys must be string literals");
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const propertyName = (property: ESTree.ObjectProperty): string | null => {
|
|
510
|
+
if (!property.computed && property.key.type === "Identifier") {
|
|
511
|
+
return property.key.name;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return property.key.type === "Literal" && typeof property.key.value === "string"
|
|
515
|
+
? property.key.value
|
|
516
|
+
: null;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const assertSupportedOptionProperty = (property: ESTree.ObjectPropertyKind): void => {
|
|
520
|
+
if (property.type === "SpreadElement") {
|
|
521
|
+
throw new Error("Translation extraction requires explicit option properties");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const name = propertyName(property);
|
|
525
|
+
|
|
526
|
+
if (name === null) {
|
|
527
|
+
throw new Error("Translation extraction requires static option property names");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (UNSUPPORTED_PLURAL_OPTION.test(name)) {
|
|
531
|
+
throw new Error("GNU gettext accepts one singular and one plural source string");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (STATIC_SOURCE_OPTION_NAMES.has(name)) {
|
|
535
|
+
assertStaticTranslationKey(property.value);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const assertSupportedOptionObject = (options: ESTree.Node | null | undefined): void => {
|
|
540
|
+
if (options === null || options === undefined) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (isStaticKeyWrapper(options) && unwrapStaticWrapper(options).type === "ObjectExpression") {
|
|
545
|
+
throw new Error("Translation option objects cannot be wrapped in another expression");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (options.type !== "ObjectExpression") {
|
|
549
|
+
throw new Error("Translation option objects must be inline object literals");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
for (const property of options.properties) {
|
|
553
|
+
assertSupportedOptionProperty(property);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const namedImportedBinding = (path: NodePath, source: string): ImportedBinding | null => {
|
|
558
|
+
if (!path.isImportSpecifier() || path.node.importKind === "type") {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const imported = path.node.imported;
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
imported: types.isIdentifier(imported) ? imported.name : imported.value,
|
|
566
|
+
local: path.node.local.name,
|
|
567
|
+
source,
|
|
568
|
+
style: "named",
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const defaultImportedBinding = (path: NodePath, source: string): ImportedBinding | null =>
|
|
573
|
+
path.isImportDefaultSpecifier()
|
|
574
|
+
? { imported: "default", local: path.node.local.name, source, style: "default" }
|
|
575
|
+
: null;
|
|
576
|
+
|
|
577
|
+
const namespaceImportedBinding = (path: NodePath, source: string): ImportedBinding | null =>
|
|
578
|
+
path.isImportNamespaceSpecifier()
|
|
579
|
+
? { imported: "*", local: path.node.local.name, source, style: "namespace" }
|
|
580
|
+
: null;
|
|
581
|
+
|
|
582
|
+
const importedBinding = (binding: ResolvedBinding | undefined): ImportedBinding | null => {
|
|
583
|
+
if (binding === undefined) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const path = binding.path;
|
|
588
|
+
const declaration = path.parentPath;
|
|
589
|
+
|
|
590
|
+
if (!declaration.isImportDeclaration() || declaration.node.importKind === "type") {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const source = declaration.node.source.value;
|
|
595
|
+
|
|
596
|
+
return namedImportedBinding(path, source) ??
|
|
597
|
+
defaultImportedBinding(path, source) ??
|
|
598
|
+
namespaceImportedBinding(path, source);
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const babelPropertyName = (property: types.ObjectProperty): string | null => {
|
|
602
|
+
const key = property.key;
|
|
603
|
+
|
|
604
|
+
if (!property.computed && types.isIdentifier(key)) {
|
|
605
|
+
return key.name;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return types.isStringLiteral(key) ? key.value : null;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const isBoundIdentifier = (node: types.Node | null, name: string): boolean => {
|
|
612
|
+
if (types.isIdentifier(node)) {
|
|
613
|
+
return node.name === name;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (types.isAssignmentPattern(node)) {
|
|
617
|
+
return types.isIdentifier(node.left) && node.left.name === name;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return false;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const isHookPatternBinding = (pattern: types.VariableDeclarator["id"], name: string): boolean => {
|
|
624
|
+
if (types.isObjectPattern(pattern)) {
|
|
625
|
+
return pattern.properties.some((property) =>
|
|
626
|
+
types.isObjectProperty(property) &&
|
|
627
|
+
babelPropertyName(property) === "t" &&
|
|
628
|
+
isBoundIdentifier(property.value, name));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (types.isArrayPattern(pattern)) {
|
|
632
|
+
return isBoundIdentifier(pattern.elements[0] ?? null, name);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return false;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const importedTranslationHook = (scope: Scope, call: types.CallExpression): ImportedBinding | null => {
|
|
639
|
+
if (!types.isIdentifier(call.callee)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const imported = importedBinding(scope.getBinding(call.callee.name));
|
|
644
|
+
|
|
645
|
+
return imported?.imported === "useTranslation" && TRANSLATION_MODULES.has(imported.source)
|
|
646
|
+
? imported
|
|
647
|
+
: null;
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const translationHookBinding = (binding: ResolvedBinding): TranslationHookBinding | null => {
|
|
651
|
+
const path = binding.path;
|
|
652
|
+
|
|
653
|
+
if (!path.isVariableDeclarator()) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const pattern = path.node.id;
|
|
658
|
+
const initializer = path.node.init;
|
|
659
|
+
|
|
660
|
+
if (!isHookPatternBinding(pattern, binding.identifier.name) ||
|
|
661
|
+
!types.isCallExpression(initializer)) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const imported = importedTranslationHook(path.scope, initializer);
|
|
666
|
+
|
|
667
|
+
if (imported === null) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return { call: initializer, isCanonical: imported.local === "useTranslation" };
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const importedTranslationFunctionKind = (imported: ImportedBinding | null): TranslationCallKind | null => {
|
|
675
|
+
if (imported?.imported !== "t" || !TRANSLATION_MODULES.has(imported.source)) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return imported.local === "t" ? "canonical" : "alias";
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const hookTranslationFunctionKind = (binding: ResolvedBinding | undefined): TranslationCallKind | null => {
|
|
683
|
+
if (binding === undefined) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const hook = translationHookBinding(binding);
|
|
688
|
+
|
|
689
|
+
if (hook === null) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return binding.identifier.name === "t" && hook.isCanonical ? "canonical" : "alias";
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
const directTranslationFunctionKind = (scope: Scope, name: string): TranslationCallKind | null => {
|
|
697
|
+
const binding = scope.getBinding(name);
|
|
698
|
+
|
|
699
|
+
return importedTranslationFunctionKind(importedBinding(binding)) ?? hookTranslationFunctionKind(binding);
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const babelMemberName = (member: TranslationMemberExpression): string | null => {
|
|
703
|
+
const property = member.property;
|
|
704
|
+
|
|
705
|
+
if (member.computed) {
|
|
706
|
+
return types.isStringLiteral(property) ? property.value : null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return types.isIdentifier(property) ? property.name : null;
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const isImportedTranslationObject = (scope: Scope, object: types.Node): boolean => {
|
|
713
|
+
if (!types.isIdentifier(object)) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const imported = importedBinding(scope.getBinding(object.name));
|
|
718
|
+
|
|
719
|
+
return imported !== null &&
|
|
720
|
+
TRANSLATION_MODULES.has(imported.source) &&
|
|
721
|
+
(imported.style === "namespace" || (imported.source === "i18next" && imported.imported === "default"));
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const constantVariableDeclarator = (binding: ResolvedBinding): NodePath<types.VariableDeclarator> | null => {
|
|
725
|
+
const path = binding.path;
|
|
726
|
+
const declaration = path.parentPath;
|
|
727
|
+
|
|
728
|
+
if (!path.isVariableDeclarator() ||
|
|
729
|
+
!declaration.isVariableDeclaration() ||
|
|
730
|
+
declaration.node.kind !== "const") {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return path;
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const isTranslationHookResultBinding = (binding: ResolvedBinding | undefined): boolean => {
|
|
738
|
+
if (binding === undefined) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const path = constantVariableDeclarator(binding);
|
|
743
|
+
const initializer = path?.node.init;
|
|
744
|
+
|
|
745
|
+
return path !== null &&
|
|
746
|
+
types.isIdentifier(path.node.id) &&
|
|
747
|
+
types.isCallExpression(initializer) &&
|
|
748
|
+
importedTranslationHook(path.scope, initializer) !== null;
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const isHookTranslationMember = (scope: Scope, member: TranslationMemberExpression): boolean => {
|
|
752
|
+
if (babelMemberName(member) !== "t") {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const object = member.object;
|
|
757
|
+
|
|
758
|
+
if (types.isCallExpression(object)) {
|
|
759
|
+
return importedTranslationHook(scope, object) !== null;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return types.isIdentifier(object) && isTranslationHookResultBinding(scope.getBinding(object.name));
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const isTranslationMember = (scope: Scope, member: TranslationMemberExpression): boolean =>
|
|
766
|
+
babelMemberName(member) === "t" &&
|
|
767
|
+
(isImportedTranslationObject(scope, member.object) || isHookTranslationMember(scope, member));
|
|
768
|
+
|
|
769
|
+
const isDirectTranslationFunction = (scope: Scope, node: types.Node): boolean =>
|
|
770
|
+
types.isIdentifier(node) && directTranslationFunctionKind(scope, node.name) !== null;
|
|
771
|
+
|
|
772
|
+
const isTranslationFunctionValue = (scope: Scope, node: types.Node): boolean => {
|
|
773
|
+
if (isDirectTranslationFunction(scope, node)) {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) &&
|
|
778
|
+
isTranslationMember(scope, node);
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
const isTranslationBindCall = (scope: Scope, node: types.Node): boolean => {
|
|
782
|
+
if (!types.isCallExpression(node) || !types.isMemberExpression(node.callee)) {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return babelMemberName(node.callee) === "bind" &&
|
|
787
|
+
isTranslationFunctionValue(scope, node.callee.object);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const isTranslationObjectPatternAlias = (
|
|
791
|
+
path: NodePath<types.VariableDeclarator>,
|
|
792
|
+
binding: ResolvedBinding,
|
|
793
|
+
): boolean => {
|
|
794
|
+
const pattern = path.node.id;
|
|
795
|
+
const initializer = path.node.init;
|
|
796
|
+
|
|
797
|
+
if (!types.isObjectPattern(pattern) ||
|
|
798
|
+
!isHookPatternBinding(pattern, binding.identifier.name) ||
|
|
799
|
+
!types.isIdentifier(initializer)) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return isImportedTranslationObject(path.scope, initializer) ||
|
|
804
|
+
isTranslationHookResultBinding(path.scope.getBinding(initializer.name));
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
const isTranslationAliasBinding = (binding: ResolvedBinding | undefined): boolean => {
|
|
808
|
+
if (binding === undefined) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const path = constantVariableDeclarator(binding);
|
|
813
|
+
|
|
814
|
+
if (path === null) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (isTranslationObjectPatternAlias(path, binding)) {
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const initializer = path.node.init;
|
|
823
|
+
|
|
824
|
+
return types.isIdentifier(path.node.id) &&
|
|
825
|
+
initializer !== null &&
|
|
826
|
+
initializer !== undefined &&
|
|
827
|
+
(isTranslationFunctionValue(path.scope, initializer) || isTranslationBindCall(path.scope, initializer));
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const translationFunctionKind = (scope: Scope, name: string): TranslationCallKind | null => {
|
|
831
|
+
const direct = directTranslationFunctionKind(scope, name);
|
|
832
|
+
|
|
833
|
+
if (direct !== null) {
|
|
834
|
+
return direct;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return isTranslationAliasBinding(scope.getBinding(name)) ? "alias" : null;
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const translationElementKind = (path: NodePath, name: string): TranslationCallKind | null => {
|
|
841
|
+
const imported = importedBinding(path.scope.getBinding(name));
|
|
842
|
+
|
|
843
|
+
if (imported?.source !== "@gtkx/i18n" || !TRANS_ELEMENTS.has(imported.imported)) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return imported.local === imported.imported ? "canonical" : "alias";
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const nodeStart = (path: NodePath): number => {
|
|
851
|
+
const start = path.node.start;
|
|
852
|
+
|
|
853
|
+
if (start === null || start === undefined) {
|
|
854
|
+
throw new Error("Translation extraction could not locate a source expression");
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return start;
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
const registerSourceMask = (node: types.Node, references: TranslationReferences): void => {
|
|
861
|
+
const { start, end } = node;
|
|
862
|
+
|
|
863
|
+
if (start === null || start === undefined || end === null || end === undefined) {
|
|
864
|
+
throw new Error("Translation extraction could not locate a source expression");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
references.masks.push({ start, end });
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const sourceLocationKey = (line: number, column: number): string => `${String(line)}:${String(column)}`;
|
|
871
|
+
|
|
872
|
+
const sourcePoint = (node: types.Node): SourcePoint | null => {
|
|
873
|
+
return locatablePoint(node);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
const registerSourceLocation = (node: types.Node, references: TranslationReferences): SourcePoint | null => {
|
|
877
|
+
const point = sourcePoint(node);
|
|
878
|
+
|
|
879
|
+
if (point !== null) {
|
|
880
|
+
references.locations.set(sourceLocationKey(point.line, point.column), point);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return point;
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const babelStaticKey = (node: types.Node | null | undefined): string | null => {
|
|
887
|
+
if (node === null || node === undefined) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (types.isStringLiteral(node)) {
|
|
892
|
+
return node.value;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (types.isTemplateLiteral(node) && node.expressions.length === 0) {
|
|
896
|
+
return node.quasis[0]?.value.cooked ?? null;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (types.isParenthesizedExpression(node) ||
|
|
900
|
+
types.isTSAsExpression(node) ||
|
|
901
|
+
types.isTSSatisfiesExpression(node) ||
|
|
902
|
+
types.isTSTypeAssertion(node) ||
|
|
903
|
+
types.isTSNonNullExpression(node)) {
|
|
904
|
+
return babelStaticKey(node.expression);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return null;
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
const babelObjectExpression = (node: types.Node | null | undefined): types.ObjectExpression | null => {
|
|
911
|
+
if (node === null || node === undefined) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (types.isObjectExpression(node)) {
|
|
916
|
+
return node;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (types.isParenthesizedExpression(node) ||
|
|
920
|
+
types.isTSAsExpression(node) ||
|
|
921
|
+
types.isTSSatisfiesExpression(node) ||
|
|
922
|
+
types.isTSTypeAssertion(node) ||
|
|
923
|
+
types.isTSNonNullExpression(node)) {
|
|
924
|
+
return babelObjectExpression(node.expression);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return null;
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const babelNamedProperty = (
|
|
931
|
+
object: types.ObjectExpression | null,
|
|
932
|
+
name: string,
|
|
933
|
+
): types.ObjectProperty | null => {
|
|
934
|
+
if (object === null) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
for (const property of object.properties) {
|
|
939
|
+
if (types.isObjectProperty(property) && babelPropertyName(property) === name) {
|
|
940
|
+
return property;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return null;
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const babelStringProperty = (object: types.ObjectExpression | null, name: string): string | null => {
|
|
948
|
+
const property = babelNamedProperty(object, name);
|
|
949
|
+
|
|
950
|
+
if (property === null) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const value = babelStaticKey(property.value);
|
|
955
|
+
|
|
956
|
+
if (value === null) {
|
|
957
|
+
throw new Error(`Translation ${name} values must be string literals`);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return value;
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
const callOptions = (call: types.CallExpression): types.ObjectExpression | null =>
|
|
964
|
+
babelObjectExpression(call.arguments[1]) ?? babelObjectExpression(call.arguments[2]);
|
|
965
|
+
|
|
966
|
+
const hookKeyPrefix = (callee: NodePath<types.Identifier>): string => {
|
|
967
|
+
const binding = callee.scope.getBinding(callee.node.name);
|
|
968
|
+
const hook = binding === undefined ? null : translationHookBinding(binding);
|
|
969
|
+
|
|
970
|
+
return babelStringProperty(
|
|
971
|
+
hook === null ? null : babelObjectExpression(hook.call.arguments[1]),
|
|
972
|
+
"keyPrefix",
|
|
973
|
+
) ?? "";
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const prefixedTranslationKey = (prefix: string, key: string): string => {
|
|
977
|
+
if (prefix.length === 0) {
|
|
978
|
+
return key;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return prefix.endsWith(".") ? `${prefix}${key}` : `${prefix}.${key}`;
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
const extractedEntryKey = (call: types.CallExpression, keyPrefix: string): string | null => {
|
|
985
|
+
const key = babelStaticKey(call.arguments[0]);
|
|
986
|
+
|
|
987
|
+
if (key === null) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const options = callOptions(call);
|
|
992
|
+
const callPrefix = babelStringProperty(options, "keyPrefix");
|
|
993
|
+
|
|
994
|
+
if (callPrefix === "" && keyPrefix.length > 0) {
|
|
995
|
+
throw new Error("An empty call keyPrefix cannot override a hook keyPrefix during extraction");
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const selectedPrefix = callPrefix ?? keyPrefix;
|
|
999
|
+
const prefixed = prefixedTranslationKey(selectedPrefix, key);
|
|
1000
|
+
const context = babelStringProperty(options, "context");
|
|
1001
|
+
|
|
1002
|
+
return context === null ? prefixed : `${prefixed}${CONTEXT_SEPARATOR}${context}`;
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
const registerEntryLocation = (
|
|
1006
|
+
key: string | null,
|
|
1007
|
+
point: SourcePoint | null,
|
|
1008
|
+
references: TranslationReferences,
|
|
1009
|
+
): void => {
|
|
1010
|
+
if (key === null || point === null) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const locations = references.entryLocations.get(key) ?? [];
|
|
1015
|
+
locations.push(point);
|
|
1016
|
+
references.entryLocations.set(key, locations);
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const registerCallReference = (
|
|
1020
|
+
callPath: NodePath<types.CallExpression>,
|
|
1021
|
+
references: TranslationReferences,
|
|
1022
|
+
keyPrefix: string,
|
|
1023
|
+
): void => {
|
|
1024
|
+
references.calls.add(nodeStart(callPath));
|
|
1025
|
+
const firstArgument = callPath.node.arguments[0];
|
|
1026
|
+
const point = registerSourceLocation(firstArgument ?? callPath.node, references);
|
|
1027
|
+
|
|
1028
|
+
if (point !== null) {
|
|
1029
|
+
references.sourcePoints.set(nodeStart(callPath), point);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
registerEntryLocation(extractedEntryKey(callPath.node, keyPrefix), point, references);
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const parserPlugins = (lang: ReturnType<typeof sourceLanguage>): ("decorators" | "jsx" | "typescript")[] => [
|
|
1036
|
+
"decorators",
|
|
1037
|
+
...(lang === "ts" || lang === "tsx" ? ["typescript" as const] : []),
|
|
1038
|
+
...(lang === "jsx" || lang === "tsx" ? ["jsx" as const] : []),
|
|
1039
|
+
];
|
|
1040
|
+
|
|
1041
|
+
const parseTranslationReferences = (
|
|
1042
|
+
root: string,
|
|
1043
|
+
path: string,
|
|
1044
|
+
source: string,
|
|
1045
|
+
lang: ReturnType<typeof sourceLanguage>,
|
|
1046
|
+
) => {
|
|
1047
|
+
try {
|
|
1048
|
+
return parseBabelSync(source, {
|
|
1049
|
+
ast: true,
|
|
1050
|
+
babelrc: false,
|
|
1051
|
+
configFile: false,
|
|
1052
|
+
filename: projectSourcePath(root, path),
|
|
1053
|
+
parserOpts: { createParenthesizedExpressions: true, plugins: parserPlugins(lang) },
|
|
1054
|
+
sourceType: "module",
|
|
1055
|
+
});
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
throw sourceExtractionError(root, path, errorSourcePoint(error), error);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const registerIdentifierTranslationCall = (
|
|
1062
|
+
callPath: NodePath<types.CallExpression>,
|
|
1063
|
+
callee: NodePath,
|
|
1064
|
+
references: TranslationReferences,
|
|
1065
|
+
): void => {
|
|
1066
|
+
if (!callee.isIdentifier()) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const kind = translationFunctionKind(callee.scope, callee.node.name);
|
|
1071
|
+
|
|
1072
|
+
if (kind === "alias") {
|
|
1073
|
+
throw new Error("Translation calls must use the imported name t");
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (kind === "canonical") {
|
|
1077
|
+
registerCallReference(callPath, references, hookKeyPrefix(callee));
|
|
1078
|
+
} else if (callee.node.name === "t") {
|
|
1079
|
+
registerSourceMask(callee.node, references);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const registerTranslationCall = (
|
|
1084
|
+
callPath: NodePath<types.CallExpression>,
|
|
1085
|
+
references: TranslationReferences,
|
|
1086
|
+
): void => {
|
|
1087
|
+
assertSupportedWrappedCallee(callPath);
|
|
1088
|
+
const callee = callPath.get("callee");
|
|
1089
|
+
|
|
1090
|
+
if (callee.isIdentifier()) {
|
|
1091
|
+
registerIdentifierTranslationCall(callPath, callee, references);
|
|
1092
|
+
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (callee.isMemberExpression() && isTranslationMember(callee.scope, callee.node)) {
|
|
1097
|
+
throw new Error("Translation calls must use the named t export");
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const unwrapTranslationCallee = (node: types.Node): types.Node => {
|
|
1102
|
+
if (types.isParenthesizedExpression(node) ||
|
|
1103
|
+
types.isTSAsExpression(node) ||
|
|
1104
|
+
types.isTSSatisfiesExpression(node) ||
|
|
1105
|
+
types.isTSTypeAssertion(node) ||
|
|
1106
|
+
types.isTSNonNullExpression(node)) {
|
|
1107
|
+
return unwrapTranslationCallee(node.expression);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return node;
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
const isTranslationCallee = (scope: Scope, node: types.Node): boolean => {
|
|
1114
|
+
if (types.isIdentifier(node)) {
|
|
1115
|
+
return translationFunctionKind(scope, node.name) !== null;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) &&
|
|
1119
|
+
isTranslationMember(scope, node);
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
const assertSupportedWrappedCallee = (callPath: NodePath<types.CallExpression>): void => {
|
|
1123
|
+
const callee = callPath.node.callee;
|
|
1124
|
+
const unwrapped = unwrapTranslationCallee(callee);
|
|
1125
|
+
|
|
1126
|
+
if (unwrapped !== callee && isTranslationCallee(callPath.scope, unwrapped)) {
|
|
1127
|
+
throw new Error("Translation call expressions cannot wrap their callee");
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
const assertSupportedOptionalCall = (callPath: NodePath<types.OptionalCallExpression>): void => {
|
|
1132
|
+
const callee = unwrapTranslationCallee(callPath.node.callee);
|
|
1133
|
+
|
|
1134
|
+
if (isTranslationCallee(callPath.scope, callee)) {
|
|
1135
|
+
throw new Error("Translation calls cannot use optional chaining");
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
const assertUnsupportedTranslationTag = (tagPath: NodePath<types.TaggedTemplateExpression>): void => {
|
|
1140
|
+
const tag = unwrapTranslationCallee(tagPath.node.tag);
|
|
1141
|
+
|
|
1142
|
+
if (isTranslationCallee(tagPath.scope, tag)) {
|
|
1143
|
+
throw new Error("Translation calls cannot use tagged-template syntax");
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
const registerUnrecognizedElementMasks = (
|
|
1148
|
+
elementPath: NodePath,
|
|
1149
|
+
name: NodePath<types.JSXIdentifier>,
|
|
1150
|
+
references: TranslationReferences,
|
|
1151
|
+
): void => {
|
|
1152
|
+
registerSourceMask(name.node, references);
|
|
1153
|
+
const parent = elementPath.parentPath.node;
|
|
1154
|
+
const closingName = types.isJSXElement(parent) ? parent.closingElement?.name : null;
|
|
1155
|
+
|
|
1156
|
+
if (types.isJSXIdentifier(closingName)) {
|
|
1157
|
+
registerSourceMask(closingName, references);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
const registerElementReference = (
|
|
1162
|
+
elementPath: NodePath,
|
|
1163
|
+
references: TranslationReferences,
|
|
1164
|
+
): void => {
|
|
1165
|
+
const start = nodeStart(elementPath);
|
|
1166
|
+
const point = registerSourceLocation(elementPath.node, references);
|
|
1167
|
+
references.elements.add(start);
|
|
1168
|
+
|
|
1169
|
+
if (point !== null) {
|
|
1170
|
+
references.sourcePoints.set(start, point);
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
const registerTranslationElement = (elementPath: NodePath, references: TranslationReferences): void => {
|
|
1175
|
+
const name = elementPath.get("name");
|
|
1176
|
+
|
|
1177
|
+
if (!name.isJSXIdentifier()) {
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const kind = translationElementKind(name, name.node.name);
|
|
1182
|
+
|
|
1183
|
+
if (kind === "alias") {
|
|
1184
|
+
throw new Error("Translation components must use their imported names");
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (kind === "canonical") {
|
|
1188
|
+
registerElementReference(elementPath, references);
|
|
1189
|
+
} else if (TRANS_ELEMENTS.has(name.node.name)) {
|
|
1190
|
+
registerUnrecognizedElementMasks(elementPath, name, references);
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const maskUnrecognizedTranslations = (source: string, references: TranslationReferences): string => {
|
|
1195
|
+
let masked = source;
|
|
1196
|
+
const masks = references.masks.toSorted((left, right) => right.start - left.start);
|
|
1197
|
+
|
|
1198
|
+
for (const { start, end } of masks) {
|
|
1199
|
+
masked = `${masked.slice(0, start)}${"_".repeat(end - start)}${masked.slice(end)}`;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
return masked;
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
const translationReferences = (
|
|
1206
|
+
root: string,
|
|
1207
|
+
path: string,
|
|
1208
|
+
source: string,
|
|
1209
|
+
lang: ReturnType<typeof sourceLanguage>,
|
|
1210
|
+
): TranslationReferences => {
|
|
1211
|
+
const parsed = parseTranslationReferences(root, path, source, lang);
|
|
1212
|
+
|
|
1213
|
+
if (parsed === null) {
|
|
1214
|
+
throw new Error(`Translation extraction could not parse ${path}`);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const references: TranslationReferences = {
|
|
1218
|
+
calls: new Set(),
|
|
1219
|
+
elements: new Set(),
|
|
1220
|
+
entryLocations: new Map(),
|
|
1221
|
+
locations: new Map(),
|
|
1222
|
+
masks: [],
|
|
1223
|
+
sourcePoints: new Map(),
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
traverse(parsed, {
|
|
1227
|
+
CallExpression(callPath) {
|
|
1228
|
+
atSourceNode(root, path, callPath.node, () => {
|
|
1229
|
+
registerTranslationCall(callPath, references);
|
|
1230
|
+
});
|
|
1231
|
+
},
|
|
1232
|
+
JSXOpeningElement(elementPath) {
|
|
1233
|
+
atSourceNode(root, path, elementPath.node, () => {
|
|
1234
|
+
registerTranslationElement(elementPath, references);
|
|
1235
|
+
});
|
|
1236
|
+
},
|
|
1237
|
+
OptionalCallExpression(callPath) {
|
|
1238
|
+
atSourceNode(root, path, callPath.node, () => {
|
|
1239
|
+
assertSupportedOptionalCall(callPath);
|
|
1240
|
+
});
|
|
1241
|
+
},
|
|
1242
|
+
TaggedTemplateExpression(tagPath) {
|
|
1243
|
+
atSourceNode(root, path, tagPath.node, () => {
|
|
1244
|
+
assertUnsupportedTranslationTag(tagPath);
|
|
1245
|
+
});
|
|
1246
|
+
},
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
return references;
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
const assertSupportedTranslationCall = (node: ESTree.CallExpression, references: TranslationReferences): void => {
|
|
1253
|
+
if (!references.calls.has(node.start)) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
assertStaticTranslationKey(node.arguments[0]);
|
|
1258
|
+
assertSupportedTranslationOptions(node.arguments[1], node.arguments[2]);
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
const assertSupportedTranslationOptions = (
|
|
1262
|
+
second: ESTree.Node | null | undefined,
|
|
1263
|
+
third: ESTree.Node | null | undefined,
|
|
1264
|
+
): void => {
|
|
1265
|
+
if (third !== undefined && third !== null) {
|
|
1266
|
+
assertStaticTranslationKey(second);
|
|
1267
|
+
assertSupportedOptionObject(third);
|
|
1268
|
+
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (second === undefined || second === null || isStaticTranslationKey(second)) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
assertSupportedOptionObject(second);
|
|
37
1277
|
};
|
|
38
1278
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1279
|
+
const isNamedJsxAttribute = (attribute: ESTree.JSXAttribute, name: string): boolean =>
|
|
1280
|
+
attribute.name.type === "JSXIdentifier" && attribute.name.name === name;
|
|
1281
|
+
|
|
1282
|
+
const assertSupportedTransKey = (attribute: ESTree.JSXAttribute): void => {
|
|
1283
|
+
if (!isNamedJsxAttribute(attribute, "i18nKey")) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const key = attribute.value?.type === "JSXExpressionContainer"
|
|
1288
|
+
? attribute.value.expression
|
|
1289
|
+
: attribute.value;
|
|
1290
|
+
assertStaticTranslationKey(key);
|
|
42
1291
|
};
|
|
43
1292
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1293
|
+
const assertSupportedTransStringAttribute = (attribute: ESTree.JSXAttribute, name: string): void => {
|
|
1294
|
+
if (!isNamedJsxAttribute(attribute, name)) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const value = attribute.value?.type === "JSXExpressionContainer"
|
|
1299
|
+
? attribute.value.expression
|
|
1300
|
+
: attribute.value;
|
|
1301
|
+
assertStaticTranslationKey(value);
|
|
50
1302
|
};
|
|
51
1303
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
shouldPreserveMetadataMessages: boolean;
|
|
57
|
-
sourceFiles: string[];
|
|
1304
|
+
const assertSupportedTransOptions = (attribute: ESTree.JSXAttribute): void => {
|
|
1305
|
+
if (isNamedJsxAttribute(attribute, "tOptions") && attribute.value?.type === "JSXExpressionContainer") {
|
|
1306
|
+
assertSupportedOptionObject(attribute.value.expression);
|
|
1307
|
+
}
|
|
58
1308
|
};
|
|
59
1309
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
1310
|
+
const assertSupportedTransAttribute = (attribute: ESTree.JSXAttributeItem): void => {
|
|
1311
|
+
if (attribute.type === "JSXSpreadAttribute") {
|
|
1312
|
+
throw new Error("Translation extraction requires explicit component properties");
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
assertSupportedTransKey(attribute);
|
|
1316
|
+
assertSupportedTransStringAttribute(attribute, "context");
|
|
1317
|
+
assertSupportedTransStringAttribute(attribute, "defaults");
|
|
1318
|
+
assertSupportedTransOptions(attribute);
|
|
64
1319
|
};
|
|
65
1320
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1321
|
+
const assertSupportedTransElement = (node: ESTree.JSXOpeningElement, references: TranslationReferences): void => {
|
|
1322
|
+
if (!references.elements.has(node.start)) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
for (const attribute of node.attributes) {
|
|
1327
|
+
assertSupportedTransAttribute(attribute);
|
|
1328
|
+
}
|
|
70
1329
|
};
|
|
71
1330
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
1331
|
+
const assertSupportedTranslationSyntax = (root: string, path: string, source: string): TranslationReferences => {
|
|
1332
|
+
const lang = sourceLanguage(path);
|
|
1333
|
+
|
|
1334
|
+
if (lang === undefined) {
|
|
1335
|
+
return {
|
|
1336
|
+
calls: new Set(),
|
|
1337
|
+
elements: new Set(),
|
|
1338
|
+
entryLocations: new Map(),
|
|
1339
|
+
locations: new Map(),
|
|
1340
|
+
masks: [],
|
|
1341
|
+
sourcePoints: new Map(),
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
76
1344
|
|
|
77
|
-
|
|
78
|
-
const reports: string[] = [];
|
|
1345
|
+
let parsed: ReturnType<typeof parseSync>;
|
|
79
1346
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
warn(message, more) {
|
|
87
|
-
const detail = more === undefined ? "" : ` ${String(more)}`;
|
|
88
|
-
reports.push(`${message}${detail}`);
|
|
89
|
-
},
|
|
90
|
-
error(message) {
|
|
91
|
-
reports.push(String(message));
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
};
|
|
1347
|
+
try {
|
|
1348
|
+
parsed = parseSync(projectSourcePath(root, path), source, { lang });
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
throw sourceExtractionError(root, path, errorSourcePoint(error), error);
|
|
1351
|
+
}
|
|
96
1352
|
|
|
97
|
-
const
|
|
98
|
-
const integration = gtkxExtractorPlugin();
|
|
1353
|
+
const references = translationReferences(root, path, source, lang);
|
|
99
1354
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
defaultNS: DEFAULT_NAMESPACE,
|
|
108
|
-
disablePlurals: false,
|
|
109
|
-
extractFromComments: false,
|
|
110
|
-
functions: [],
|
|
111
|
-
generateBasePluralForms: false,
|
|
112
|
-
keySeparator: ".",
|
|
113
|
-
nsSeparator: false,
|
|
114
|
-
pluralSeparator: "_",
|
|
115
|
-
transComponents: integration.transComponents,
|
|
116
|
-
useTranslationNames: [],
|
|
117
|
-
warnOnConflicts: "error",
|
|
1355
|
+
new Visitor({
|
|
1356
|
+
CallExpression(node) {
|
|
1357
|
+
try {
|
|
1358
|
+
assertSupportedTranslationCall(node, references);
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
throw sourceExtractionError(root, path, references.sourcePoints.get(node.start) ?? null, error);
|
|
1361
|
+
}
|
|
118
1362
|
},
|
|
119
|
-
|
|
1363
|
+
JSXOpeningElement(node) {
|
|
1364
|
+
try {
|
|
1365
|
+
assertSupportedTransElement(node, references);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
throw sourceExtractionError(root, path, references.sourcePoints.get(node.start) ?? null, error);
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1370
|
+
}).visit(parsed.program);
|
|
1371
|
+
|
|
1372
|
+
return references;
|
|
120
1373
|
};
|
|
121
1374
|
|
|
122
|
-
const
|
|
123
|
-
|
|
1375
|
+
const extractedLocationFile = (root: string, file: string): string =>
|
|
1376
|
+
resolve(root, file);
|
|
124
1377
|
|
|
125
|
-
|
|
126
|
-
|
|
1378
|
+
const isRecognizedLocation = (
|
|
1379
|
+
root: string,
|
|
1380
|
+
references: Map<string, TranslationReferences>,
|
|
1381
|
+
location: SourceLocation,
|
|
1382
|
+
): boolean => {
|
|
1383
|
+
if (location.line === undefined || location.column === undefined) {
|
|
1384
|
+
return false;
|
|
127
1385
|
}
|
|
128
1386
|
|
|
129
|
-
|
|
1387
|
+
const sourceReferences = references.get(extractedLocationFile(root, location.file));
|
|
1388
|
+
|
|
1389
|
+
return sourceReferences?.locations.has(sourceLocationKey(location.line, location.column)) === true;
|
|
130
1390
|
};
|
|
131
1391
|
|
|
132
|
-
const
|
|
133
|
-
|
|
1392
|
+
const recognizedKeyLocations = (
|
|
1393
|
+
references: Map<string, TranslationReferences>,
|
|
1394
|
+
extractedKey: string,
|
|
1395
|
+
): ExtractedLocation[] => {
|
|
1396
|
+
const locations: ExtractedLocation[] = [];
|
|
134
1397
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1398
|
+
for (const [file, sourceReferences] of references) {
|
|
1399
|
+
const points = sourceReferences.entryLocations.get(extractedKey) ?? [];
|
|
1400
|
+
|
|
1401
|
+
for (const point of points) {
|
|
1402
|
+
locations.push({ file, column: point.column, line: point.line });
|
|
1403
|
+
}
|
|
138
1404
|
}
|
|
1405
|
+
|
|
1406
|
+
return locations;
|
|
139
1407
|
};
|
|
140
1408
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1409
|
+
const recognizedEntry = (
|
|
1410
|
+
root: string,
|
|
1411
|
+
references: Map<string, TranslationReferences>,
|
|
1412
|
+
entry: ExtractedKey,
|
|
1413
|
+
): ExtractedKey | null => {
|
|
1414
|
+
const extractedLocations = entry.locations ?? [];
|
|
1415
|
+
const locations = extractedLocations.length === 0
|
|
1416
|
+
? recognizedKeyLocations(references, entry.key)
|
|
1417
|
+
: extractedLocations.filter((location) => isRecognizedLocation(root, references, location));
|
|
1418
|
+
|
|
1419
|
+
return locations.length === 0 ? null : { ...entry, locations };
|
|
1420
|
+
};
|
|
145
1421
|
|
|
146
|
-
|
|
147
|
-
const unique: Map<string, SourceLocation> = new Map();
|
|
1422
|
+
type NestedReferenceParts = { base: string; options: string };
|
|
148
1423
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
unique.set(key, location);
|
|
152
|
-
}
|
|
1424
|
+
const quotedNestedReferenceParts = (content: string, quote: string): NestedReferenceParts | null => {
|
|
1425
|
+
const end = content.indexOf(quote, 1);
|
|
153
1426
|
|
|
154
|
-
return
|
|
1427
|
+
return end === -1
|
|
1428
|
+
? null
|
|
1429
|
+
: { base: content.slice(1, end), options: content.slice(end + 1) };
|
|
155
1430
|
};
|
|
156
1431
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
1432
|
+
const nestedReferenceParts = (value: string): NestedReferenceParts | null => {
|
|
1433
|
+
const content = value.trim();
|
|
1434
|
+
const quote = content[0];
|
|
1435
|
+
|
|
1436
|
+
if (quote === "\"" || quote === "'") {
|
|
1437
|
+
return quotedNestedReferenceParts(content, quote);
|
|
160
1438
|
}
|
|
161
1439
|
|
|
162
|
-
|
|
1440
|
+
const separator = content.indexOf(",");
|
|
1441
|
+
const base = (separator === -1 ? content : content.slice(0, separator)).trim();
|
|
1442
|
+
|
|
1443
|
+
return base.length === 0
|
|
1444
|
+
? null
|
|
1445
|
+
: { base, options: separator === -1 ? "" : content.slice(separator + 1) };
|
|
163
1446
|
};
|
|
164
1447
|
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
const isDefaultExplicit = entry.explicitDefault === true;
|
|
169
|
-
const singular = pointSource(entry, msgid);
|
|
1448
|
+
const nestedReferenceContext = (options: string): string | null => {
|
|
1449
|
+
const match = NESTED_CONTEXT.exec(options);
|
|
1450
|
+
const context = match?.[1] ?? match?.[2] ?? null;
|
|
170
1451
|
|
|
171
|
-
|
|
172
|
-
|
|
1452
|
+
return context === null || context.length === 0 ? null : context;
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
const adjustedParenthesisDepth = (character: string | undefined, depth: number): number => {
|
|
1456
|
+
if (character === "(") {
|
|
1457
|
+
return depth + 1;
|
|
173
1458
|
}
|
|
174
1459
|
|
|
175
|
-
return
|
|
176
|
-
context,
|
|
177
|
-
kind: isDefaultExplicit ? "default" : "point",
|
|
178
|
-
locations: normalizedLocations(entry.locations ?? []),
|
|
179
|
-
msgid,
|
|
180
|
-
namespace: namespaceFor(entry),
|
|
181
|
-
plural: null,
|
|
182
|
-
singular,
|
|
183
|
-
};
|
|
1460
|
+
return character === ")" ? depth - 1 : depth;
|
|
184
1461
|
};
|
|
185
1462
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
1463
|
+
const nestedReferenceEnd = (text: string, start: number): number => {
|
|
1464
|
+
let depth = 0;
|
|
1465
|
+
const tokens = text.slice(start).matchAll(NESTED_STRUCTURE_TOKEN);
|
|
190
1466
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
const category = suffix?.[1];
|
|
1467
|
+
for (const token of tokens) {
|
|
1468
|
+
const character = token[0];
|
|
194
1469
|
|
|
195
|
-
|
|
196
|
-
|
|
1470
|
+
if (character === ")" && depth === 0) {
|
|
1471
|
+
return start + token.index;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
depth = adjustedParenthesisDepth(character, depth);
|
|
197
1475
|
}
|
|
198
1476
|
|
|
199
|
-
return
|
|
200
|
-
baseKey: entry.key.slice(0, -suffix[0].length),
|
|
201
|
-
variant: {
|
|
202
|
-
category,
|
|
203
|
-
defaultValue,
|
|
204
|
-
locations: entry.locations ?? [],
|
|
205
|
-
},
|
|
206
|
-
};
|
|
1477
|
+
return -1;
|
|
207
1478
|
};
|
|
208
1479
|
|
|
209
|
-
const
|
|
210
|
-
const
|
|
1480
|
+
const nestedReferenceContents = (text: string): string[] => {
|
|
1481
|
+
const contents: string[] = [];
|
|
1482
|
+
let offset = 0;
|
|
211
1483
|
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
1484
|
+
while (offset < text.length) {
|
|
1485
|
+
const prefix = text.indexOf("$t(", offset);
|
|
1486
|
+
|
|
1487
|
+
if (prefix === -1) {
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
const start = prefix + 3;
|
|
1492
|
+
const end = nestedReferenceEnd(text, start);
|
|
1493
|
+
|
|
1494
|
+
if (end === -1) {
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
contents.push(text.slice(start, end));
|
|
1499
|
+
offset = end + 1;
|
|
219
1500
|
}
|
|
220
1501
|
|
|
221
|
-
return
|
|
1502
|
+
return contents;
|
|
222
1503
|
};
|
|
223
1504
|
|
|
224
|
-
const
|
|
225
|
-
const
|
|
1505
|
+
const nestedPluralKeys = (base: string, context: string | null): string[] => {
|
|
1506
|
+
const pluralBase = context === null ? base : `${base}${CONTEXT_SEPARATOR}${context}`;
|
|
226
1507
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
1508
|
+
return [`${pluralBase}_one`, `${pluralBase}_other`];
|
|
1509
|
+
};
|
|
230
1510
|
|
|
231
|
-
|
|
232
|
-
const
|
|
1511
|
+
const nestedReferenceKeys = (content: string): string[] => {
|
|
1512
|
+
const parts = nestedReferenceParts(content);
|
|
233
1513
|
|
|
234
|
-
if (
|
|
235
|
-
|
|
1514
|
+
if (parts === null) {
|
|
1515
|
+
return [];
|
|
236
1516
|
}
|
|
237
1517
|
|
|
238
|
-
|
|
1518
|
+
const context = nestedReferenceContext(parts.options);
|
|
1519
|
+
const contextual = context === null
|
|
1520
|
+
? []
|
|
1521
|
+
: [`${parts.base}_${context}`, `${parts.base}${CONTEXT_SEPARATOR}${context}`];
|
|
1522
|
+
|
|
1523
|
+
return NESTED_COUNT.test(parts.options)
|
|
1524
|
+
? [parts.base, ...contextual, ...nestedPluralKeys(parts.base, context)]
|
|
1525
|
+
: [parts.base, ...contextual];
|
|
239
1526
|
};
|
|
240
1527
|
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const { context, msgid } = splitContext(baseKey);
|
|
244
|
-
validateIdentity(context, msgid);
|
|
245
|
-
const hasStableKey = one.defaultValue !== other.defaultValue;
|
|
246
|
-
const singular = hasStableKey ? one.defaultValue : msgid;
|
|
247
|
-
const plural = other.defaultValue;
|
|
1528
|
+
const nestedEntryKeys = (text: string): Set<string> => {
|
|
1529
|
+
const keys: Set<string> = new Set();
|
|
248
1530
|
|
|
249
|
-
|
|
250
|
-
|
|
1531
|
+
for (const content of nestedReferenceContents(text)) {
|
|
1532
|
+
for (const key of nestedReferenceKeys(content)) {
|
|
1533
|
+
keys.add(key);
|
|
1534
|
+
}
|
|
251
1535
|
}
|
|
252
1536
|
|
|
253
|
-
return
|
|
254
|
-
context,
|
|
255
|
-
kind: hasStableKey ? "pluralDefaults" : "plural",
|
|
256
|
-
locations: normalizedLocations(variants.flatMap((variant) => variant.locations)),
|
|
257
|
-
msgid,
|
|
258
|
-
namespace,
|
|
259
|
-
plural,
|
|
260
|
-
singular,
|
|
261
|
-
};
|
|
1537
|
+
return keys;
|
|
262
1538
|
};
|
|
263
1539
|
|
|
264
|
-
const
|
|
265
|
-
|
|
1540
|
+
const registerNestedKeyLocations = (
|
|
1541
|
+
root: string,
|
|
1542
|
+
references: Map<string, TranslationReferences>,
|
|
1543
|
+
key: string,
|
|
1544
|
+
locations: ExtractedLocation[],
|
|
1545
|
+
): void => {
|
|
1546
|
+
for (const location of locations) {
|
|
1547
|
+
if (location.line === undefined || location.column === undefined) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
266
1550
|
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
1551
|
+
const sourceReferences = references.get(resolve(root, location.file));
|
|
1552
|
+
|
|
1553
|
+
if (sourceReferences !== undefined) {
|
|
1554
|
+
registerEntryLocation(key, { column: location.column, line: location.line }, sourceReferences);
|
|
1555
|
+
}
|
|
270
1556
|
}
|
|
271
1557
|
};
|
|
272
1558
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
1559
|
+
const registerNestedTextLocations = (
|
|
1560
|
+
root: string,
|
|
1561
|
+
references: Map<string, TranslationReferences>,
|
|
1562
|
+
text: string,
|
|
1563
|
+
locations: ExtractedLocation[],
|
|
1564
|
+
): void => {
|
|
1565
|
+
for (const key of nestedEntryKeys(text)) {
|
|
1566
|
+
registerNestedKeyLocations(root, references, key, locations);
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
276
1569
|
|
|
277
|
-
|
|
278
|
-
|
|
1570
|
+
const registerNestedEntryLocations = (
|
|
1571
|
+
root: string,
|
|
1572
|
+
references: Map<string, TranslationReferences>,
|
|
1573
|
+
entry: ExtractedKey,
|
|
1574
|
+
locations: ExtractedLocation[],
|
|
1575
|
+
): void => {
|
|
1576
|
+
registerNestedTextLocations(root, references, entry.key, locations);
|
|
279
1577
|
|
|
280
|
-
|
|
281
|
-
|
|
1578
|
+
if (typeof entry.defaultValue === "string") {
|
|
1579
|
+
registerNestedTextLocations(root, references, entry.defaultValue, locations);
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
const didRegisterNestedLocationPass = (
|
|
1584
|
+
root: string,
|
|
1585
|
+
references: Map<string, TranslationReferences>,
|
|
1586
|
+
entries: ExtractedKeysMap,
|
|
1587
|
+
processed: Set<string>,
|
|
1588
|
+
): boolean => {
|
|
1589
|
+
let didRegister = false;
|
|
1590
|
+
|
|
1591
|
+
for (const [mapKey, entry] of entries) {
|
|
1592
|
+
if (processed.has(mapKey)) {
|
|
282
1593
|
continue;
|
|
283
1594
|
}
|
|
284
1595
|
|
|
285
|
-
|
|
286
|
-
|
|
1596
|
+
const recognized = recognizedEntry(root, references, entry);
|
|
1597
|
+
|
|
1598
|
+
if (recognized !== null) {
|
|
1599
|
+
processed.add(mapKey);
|
|
1600
|
+
registerNestedEntryLocations(root, references, entry, recognized.locations ?? []);
|
|
1601
|
+
didRegister = true;
|
|
287
1602
|
}
|
|
1603
|
+
}
|
|
288
1604
|
|
|
289
|
-
|
|
1605
|
+
return didRegister;
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
const registerNestedLocations = (
|
|
1609
|
+
root: string,
|
|
1610
|
+
references: Map<string, TranslationReferences>,
|
|
1611
|
+
entries: ExtractedKeysMap,
|
|
1612
|
+
): void => {
|
|
1613
|
+
const processed: Set<string> = new Set();
|
|
1614
|
+
let hasRegisteredLocations = true;
|
|
1615
|
+
|
|
1616
|
+
while (hasRegisteredLocations) {
|
|
1617
|
+
hasRegisteredLocations = didRegisterNestedLocationPass(root, references, entries, processed);
|
|
290
1618
|
}
|
|
1619
|
+
};
|
|
291
1620
|
|
|
292
|
-
|
|
1621
|
+
const retainRecognizedEntries = (
|
|
1622
|
+
root: string,
|
|
1623
|
+
references: Map<string, TranslationReferences>,
|
|
1624
|
+
entries: ExtractedKeysMap,
|
|
1625
|
+
): void => {
|
|
1626
|
+
for (const [mapKey, entry] of entries) {
|
|
1627
|
+
const recognized = recognizedEntry(root, references, entry);
|
|
1628
|
+
|
|
1629
|
+
if (recognized === null) {
|
|
1630
|
+
entries.delete(mapKey);
|
|
1631
|
+
} else {
|
|
1632
|
+
entries.set(mapKey, recognized);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
293
1635
|
};
|
|
294
1636
|
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
(left.context ?? "").localeCompare(right.context ?? "") ||
|
|
298
|
-
left.msgid.localeCompare(right.msgid) ||
|
|
299
|
-
left.singular.localeCompare(right.singular) ||
|
|
300
|
-
(left.plural ?? "").localeCompare(right.plural ?? "");
|
|
1637
|
+
const fileSourcePointKey = (file: string, line: number, column: number): string =>
|
|
1638
|
+
`${file}\0${sourceLocationKey(line, column)}`;
|
|
301
1639
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
1640
|
+
const registerExtractedSourcePoint = (
|
|
1641
|
+
root: string,
|
|
1642
|
+
points: Set<string>,
|
|
1643
|
+
location: ExtractedLocation,
|
|
1644
|
+
): void => {
|
|
1645
|
+
if (location.line !== undefined && location.column !== undefined) {
|
|
1646
|
+
points.add(fileSourcePointKey(resolve(root, location.file), location.line, location.column));
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
307
1649
|
|
|
308
|
-
|
|
309
|
-
|
|
1650
|
+
const extractedSourcePoints = (root: string, entries: ExtractedKeysMap): Set<string> => {
|
|
1651
|
+
const points: Set<string> = new Set();
|
|
310
1652
|
|
|
311
|
-
|
|
1653
|
+
for (const entry of entries.values()) {
|
|
1654
|
+
const locations = entry.locations ?? [];
|
|
1655
|
+
|
|
1656
|
+
for (const location of locations) {
|
|
1657
|
+
registerExtractedSourcePoint(root, points, location);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
return points;
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
const missingSourcePoint = (
|
|
1665
|
+
file: string,
|
|
1666
|
+
references: TranslationReferences,
|
|
1667
|
+
extracted: Set<string>,
|
|
1668
|
+
): SourcePoint | null => {
|
|
1669
|
+
for (const [location, point] of references.locations) {
|
|
1670
|
+
if (!extracted.has(`${file}\0${location}`)) {
|
|
1671
|
+
return point;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
return null;
|
|
312
1676
|
};
|
|
313
1677
|
|
|
314
|
-
const
|
|
1678
|
+
const assertAllReferencesExtracted = (
|
|
1679
|
+
root: string,
|
|
1680
|
+
references: Map<string, TranslationReferences>,
|
|
1681
|
+
entries: ExtractedKeysMap,
|
|
1682
|
+
): void => {
|
|
1683
|
+
const extracted = extractedSourcePoints(root, entries);
|
|
1684
|
+
|
|
1685
|
+
for (const [file, sourceReferences] of references) {
|
|
1686
|
+
const point = missingSourcePoint(file, sourceReferences, extracted);
|
|
1687
|
+
|
|
1688
|
+
if (point !== null) {
|
|
1689
|
+
throw sourceExtractionError(
|
|
1690
|
+
root,
|
|
1691
|
+
file,
|
|
1692
|
+
point,
|
|
1693
|
+
new Error("A translation expression could not be extracted safely"),
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
const findSourceMessages = async (root: string, sourceFiles: string[]): Promise<SourceMessage[]> => {
|
|
315
1700
|
if (sourceFiles.length === 0) {
|
|
1701
|
+
clearI18nResources(root);
|
|
1702
|
+
|
|
316
1703
|
return [];
|
|
317
1704
|
}
|
|
318
1705
|
|
|
319
|
-
const
|
|
1706
|
+
const sources = new Map(sourceFiles.map((file) => [file, readFileSync(file, "utf8")]));
|
|
1707
|
+
const references: Map<string, TranslationReferences> = new Map();
|
|
1708
|
+
|
|
1709
|
+
for (const [file, source] of sources) {
|
|
1710
|
+
references.set(file, assertSupportedTranslationSyntax(root, file, source));
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
let extracted: ExtractedKeysMap | undefined;
|
|
320
1714
|
const { logger, reports } = quietLogger();
|
|
321
|
-
const { allKeys } = await findKeys(extractorConfig(sourceFiles), logger, fileErrors);
|
|
322
1715
|
|
|
323
|
-
|
|
324
|
-
|
|
1716
|
+
const capture: Plugin = {
|
|
1717
|
+
name: "gtkx-gettext",
|
|
1718
|
+
onLoad(code, path) {
|
|
1719
|
+
const sourceReferences = references.get(resolve(root, path));
|
|
1720
|
+
|
|
1721
|
+
return sourceReferences === undefined
|
|
1722
|
+
? code
|
|
1723
|
+
: maskUnrecognizedTranslations(code, sourceReferences);
|
|
1724
|
+
},
|
|
1725
|
+
onEnd(keys) {
|
|
1726
|
+
registerNestedLocations(root, references, keys);
|
|
1727
|
+
retainRecognizedEntries(root, references, keys);
|
|
1728
|
+
assertAllReferencesExtracted(root, references, keys);
|
|
1729
|
+
extracted = keys;
|
|
1730
|
+
},
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1733
|
+
const result = await runExtractor(i18nToolkitConfig(root, sourceFiles, [capture]), {
|
|
1734
|
+
logger,
|
|
1735
|
+
quiet: true,
|
|
1736
|
+
syncPrimaryWithDefaults: true,
|
|
1737
|
+
trustDerivedDefaults: true,
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
if (extracted === undefined || reports.length > 0 || result.hasErrors) {
|
|
1741
|
+
throw new Error(reports.join("\n") || "i18next extraction failed");
|
|
325
1742
|
}
|
|
326
1743
|
|
|
327
|
-
return sourceMessages(
|
|
1744
|
+
return sourceMessages(root, extracted.values().toArray())
|
|
1745
|
+
.map((message) => inferLocations(message, sources))
|
|
1746
|
+
.toSorted(compareMessages);
|
|
328
1747
|
};
|
|
329
1748
|
|
|
330
1749
|
const projectPath = (root: string, path: string): string | null => {
|
|
331
1750
|
const absolute = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
332
1751
|
const projectRelative = relative(root, absolute);
|
|
333
1752
|
|
|
334
|
-
if (
|
|
335
|
-
projectRelative === "" ||
|
|
336
|
-
projectRelative === ".." ||
|
|
337
|
-
projectRelative.startsWith(`..${sep}`) ||
|
|
338
|
-
isAbsolute(projectRelative)
|
|
339
|
-
) {
|
|
1753
|
+
if (!isPathInside(root, absolute)) {
|
|
340
1754
|
return null;
|
|
341
1755
|
}
|
|
342
1756
|
|
|
343
|
-
return projectRelative
|
|
1757
|
+
return toPosixPath(projectRelative);
|
|
344
1758
|
};
|
|
345
1759
|
|
|
346
1760
|
const writePotfiles = (project: CatalogProject, sourceFiles: string[]): string => {
|
|
@@ -371,9 +1785,6 @@ const renderCatalogCall = (message: SourceMessage): string => {
|
|
|
371
1785
|
: `pgettext(${JSON.stringify(message.context)}, ${msgid});`;
|
|
372
1786
|
};
|
|
373
1787
|
|
|
374
|
-
const readSources = (sourceFiles: string[]): Map<string, string> =>
|
|
375
|
-
new Map(sourceFiles.map((path) => [path, readFileSync(path, "utf8")]));
|
|
376
|
-
|
|
377
1788
|
const locatedOwners = (
|
|
378
1789
|
project: CatalogProject,
|
|
379
1790
|
message: SourceMessage,
|
|
@@ -393,49 +1804,23 @@ const locatedOwners = (
|
|
|
393
1804
|
return unique.values().toArray();
|
|
394
1805
|
};
|
|
395
1806
|
|
|
396
|
-
const inferredOwner = (
|
|
397
|
-
project: CatalogProject,
|
|
398
|
-
message: SourceMessage,
|
|
399
|
-
sources: Map<string, string>,
|
|
400
|
-
): { path: string; line: number } | null => {
|
|
401
|
-
for (const [path, source] of sources) {
|
|
402
|
-
const index = source.indexOf(message.msgid);
|
|
403
|
-
|
|
404
|
-
if (index !== -1) {
|
|
405
|
-
return {
|
|
406
|
-
path: projectPath(project.root, path) ?? SYNTHETIC_FILENAME,
|
|
407
|
-
line: source.slice(0, index).split("\n").length,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return null;
|
|
413
|
-
};
|
|
414
|
-
|
|
415
1807
|
const sourceOwners = (
|
|
416
1808
|
project: CatalogProject,
|
|
417
1809
|
message: SourceMessage,
|
|
418
|
-
sources: Map<string, string>,
|
|
419
1810
|
): { path: string; line: number | undefined }[] => {
|
|
420
1811
|
const located = locatedOwners(project, message);
|
|
421
1812
|
|
|
422
|
-
|
|
423
|
-
return located;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return [inferredOwner(project, message, sources) ?? { path: SYNTHETIC_FILENAME, line: undefined }];
|
|
1813
|
+
return located.length > 0 ? located : [{ path: SYNTHETIC_FILENAME, line: undefined }];
|
|
427
1814
|
};
|
|
428
1815
|
|
|
429
1816
|
const syntheticEntries = (
|
|
430
1817
|
project: CatalogProject,
|
|
431
1818
|
messages: SourceMessage[],
|
|
432
|
-
sourceFiles: string[],
|
|
433
1819
|
): Map<string, SyntheticEntry[]> => {
|
|
434
|
-
const sources = readSources(sourceFiles);
|
|
435
1820
|
const entries: Map<string, SyntheticEntry[]> = new Map();
|
|
436
1821
|
|
|
437
1822
|
for (const message of messages) {
|
|
438
|
-
for (const owner of sourceOwners(project, message
|
|
1823
|
+
for (const owner of sourceOwners(project, message)) {
|
|
439
1824
|
const owned = entries.get(owner.path) ?? [];
|
|
440
1825
|
owned.push({ call: renderCatalogCall(message), line: owner.line });
|
|
441
1826
|
entries.set(owner.path, owned);
|
|
@@ -492,9 +1877,8 @@ const writeSyntheticSources = (
|
|
|
492
1877
|
workDir: string,
|
|
493
1878
|
project: CatalogProject,
|
|
494
1879
|
messages: SourceMessage[],
|
|
495
|
-
sourceFiles: string[],
|
|
496
1880
|
): string => {
|
|
497
|
-
const entries = syntheticEntries(project, messages
|
|
1881
|
+
const entries = syntheticEntries(project, messages);
|
|
498
1882
|
const paths: string[] = [];
|
|
499
1883
|
|
|
500
1884
|
for (const [path, owned] of entries) {
|
|
@@ -510,8 +1894,8 @@ const writeSyntheticSources = (
|
|
|
510
1894
|
return potfiles;
|
|
511
1895
|
};
|
|
512
1896
|
|
|
513
|
-
const extractSourceMessages = ({ project, messages,
|
|
514
|
-
const potfilesPath = writeSyntheticSources(workDir, project, messages
|
|
1897
|
+
const extractSourceMessages = ({ project, messages, output, workDir }: SourceExtraction): void => {
|
|
1898
|
+
const potfilesPath = writeSyntheticSources(workDir, project, messages);
|
|
515
1899
|
|
|
516
1900
|
runCliTool({
|
|
517
1901
|
tool: "xgettext",
|
|
@@ -555,7 +1939,6 @@ const joinMetadataFragment = (output: string, fragment: string): void => {
|
|
|
555
1939
|
const extractCatalogTemplate = ({
|
|
556
1940
|
project,
|
|
557
1941
|
messages,
|
|
558
|
-
sourceFiles,
|
|
559
1942
|
shouldPreserveMetadataMessages,
|
|
560
1943
|
output = resolve(project.poDir, `${project.domain}.pot`),
|
|
561
1944
|
}: CatalogTemplateExtraction): void => {
|
|
@@ -567,10 +1950,10 @@ const extractCatalogTemplate = ({
|
|
|
567
1950
|
if (shouldPreserveMetadataMessages && hasPreviousTemplate) {
|
|
568
1951
|
const fragment = join(workDir, "metadata.pot");
|
|
569
1952
|
extractMetadataFragment(project, output, fragment);
|
|
570
|
-
extractSourceMessages({ project, messages,
|
|
1953
|
+
extractSourceMessages({ project, messages, output: source, workDir });
|
|
571
1954
|
joinMetadataFragment(source, fragment);
|
|
572
1955
|
} else {
|
|
573
|
-
extractSourceMessages({ project, messages,
|
|
1956
|
+
extractSourceMessages({ project, messages, output: source, workDir });
|
|
574
1957
|
}
|
|
575
1958
|
|
|
576
1959
|
replaceCatalogTemplate(source, output);
|
|
@@ -583,26 +1966,22 @@ const extractSourceCatalogTo = async (
|
|
|
583
1966
|
project: CatalogProject,
|
|
584
1967
|
paths: string[],
|
|
585
1968
|
output: string,
|
|
586
|
-
): Promise<
|
|
1969
|
+
): Promise<void> => {
|
|
587
1970
|
const sourceFiles = normalizedSourceFiles(project.root, paths);
|
|
588
|
-
const messages = await findSourceMessages(sourceFiles);
|
|
1971
|
+
const messages = await findSourceMessages(project.root, sourceFiles);
|
|
589
1972
|
writePotfiles(project, sourceFiles);
|
|
590
|
-
extractCatalogTemplate({ project, messages,
|
|
591
|
-
|
|
592
|
-
return { messages, sourceFiles };
|
|
1973
|
+
extractCatalogTemplate({ project, messages, shouldPreserveMetadataMessages: false, output });
|
|
593
1974
|
};
|
|
594
1975
|
|
|
595
1976
|
const extractSourceCatalog = async (
|
|
596
1977
|
project: CatalogProject,
|
|
597
1978
|
paths: string[],
|
|
598
1979
|
shouldPreserveMetadataMessages = true,
|
|
599
|
-
): Promise<
|
|
1980
|
+
): Promise<void> => {
|
|
600
1981
|
const sourceFiles = normalizedSourceFiles(project.root, paths);
|
|
601
|
-
const messages = await findSourceMessages(sourceFiles);
|
|
1982
|
+
const messages = await findSourceMessages(project.root, sourceFiles);
|
|
602
1983
|
writePotfiles(project, sourceFiles);
|
|
603
|
-
extractCatalogTemplate({ project, messages,
|
|
604
|
-
|
|
605
|
-
return { messages, sourceFiles };
|
|
1984
|
+
extractCatalogTemplate({ project, messages, shouldPreserveMetadataMessages });
|
|
606
1985
|
};
|
|
607
1986
|
|
|
608
|
-
export {
|
|
1987
|
+
export { extractSourceCatalog, extractSourceCatalogTo, SourceExtractionError };
|