Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new package with vue extractor #1578

Merged
merged 3 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ module.exports = {
".*.js.snap$",
],
coverageReporters: ["lcov", "text"],

globalSetup: "./scripts/jest/setupTimezone.js",
projects: [
{
Expand Down Expand Up @@ -70,6 +69,7 @@ module.exports = {
"<rootDir>/packages/format-json",
"<rootDir>/packages/format-csv",
"<rootDir>/packages/message-utils",
"<rootDir>/packages/extractor-vue",
],
},
],
Expand Down
9 changes: 2 additions & 7 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"version": "4.0.0-next.7",
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
Expand All @@ -19,10 +17,7 @@
]
},
"publish": {
"allowBranch": [
"main",
"next"
],
"allowBranch": ["main", "next"],
"ignoreChanges": [
"**/CHANGELOG.md",
"**/examples/*",
Expand Down
46 changes: 46 additions & 0 deletions packages/extractor-vue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[![License][badge-license]][license]
[![Version][badge-version]][package]
[![Downloads][badge-downloads]][package]

# @lingui/vue-extractor

This package contains a custom extractor that handles Vue.js files. It supports extracting messages from script and setup scripts as well as Vue templates.

`@lingui/vue-extractor` is part of [LinguiJS][linguijs]. See the [documentation][documentation] for all information, tutorials and examples.

## Installation

```sh
npm install --save-dev @lingui/extractor-vue
```

## Usage

This custom extractor requires that you use JavaScript for your Lingui configuration.

```js
import { vueExtractor } from "@lingui/extractor-vue"
import babel from "@lingui/cli/api/extractors/babel"

const linguiConfig = {
locales: ["en", "nb"],
sourceLocale: "en",
catalogs: [
{
path: "<rootDir>/src/{locale}",
include: ["<rootDir>/src"],
},
],
extractors: [babel, vueExtractor],
}

export default linguiConfig
```

## License

This package is licensed under [MIT][license] license.

[license]: https://github.com/lingui/js-lingui/blob/main/LICENSE
[linguijs]: https://github.com/lingui/js-lingui
[documentation]: https://lingui.dev/tutorials/extractor-vue
41 changes: 41 additions & 0 deletions packages/extractor-vue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@lingui/extractor-vue",
"version": "4.0.0-next.7",
"description": "Custom Vue.js extractor to be used with the CLI tool",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"keywords": [
"cli",
"i18n",
"internationalization",
"i10n",
"localization",
"i9n",
"translation",
"vue"
],
"repository": "lingui/js-lingui",
"bugs": "https://github.com/lingui/js-lingui/issues",
"license": "MIT",
"author": {
"name": "Christoffer Jahren",
"email": "christoffer@jahren.it"
},
"scripts": {
"build": "rimraf ./dist && unbuild",
"stub": "unbuild --stub"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@lingui/cli": "^4.0.0-next.7",
"@lingui/conf": "^4.0.0-next.7",
"@vue/compiler-sfc": "^3.2.47"
},
"devDependencies": {
"@lingui/babel-plugin-extract-messages": "^4.0.0-next.7",
"unbuild": "^1.1.2"
}
}
72 changes: 72 additions & 0 deletions packages/extractor-vue/src/__snapshots__/extractor.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`vue extractor should extract message from vue file 1`] = `
[
{
comment: undefined,
context: undefined,
id: Setup message,
message: undefined,
origin: [
test.vue.ts,
4,
0,
],
},
{
comment: undefined,
context: undefined,
id: Script message,
message: undefined,
origin: [
test.vue,
19,
20,
],
},
{
comment: undefined,
context: undefined,
id: custom.id,
message: My message,
origin: [
test.vue,
27,
11,
],
},
{
comment: Message comment,
context: undefined,
id: my.message,
message: My descriptor message,
origin: [
test.vue,
29,
10,
],
},
{
comment: undefined,
context: undefined,
id: id used as message,
message: undefined,
origin: [
test.vue,
35,
5,
],
},
{
comment: undefined,
context: undefined,
id: My message without ID and context,
message: undefined,
origin: [
test.vue,
36,
11,
],
},
]
`;
64 changes: 64 additions & 0 deletions packages/extractor-vue/src/extractor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { makeConfig } from "@lingui/conf"
import fs from "fs"
import path from "path"
import { vueExtractor } from "."
import type { ExtractedMessage } from "@lingui/babel-plugin-extract-messages"

function normalizePath(entries: ExtractedMessage[]): ExtractedMessage[] {
return entries.map((entry) => {
const [filename, lineNumber, column] = entry.origin
const projectRoot = process.cwd()

return {
...entry,
origin: [path.relative(projectRoot, filename ?? ""), lineNumber, column],
}
})
}

describe("vue extractor", () => {
const linguiConfig = makeConfig({
locales: ["en", "nb"],
sourceLocale: "en",
rootDir: ".",
catalogs: [
{
path: "<rootDir>/{locale}",
include: ["<rootDir>"],
exclude: [],
},
],
extractorParserOptions: {
tsExperimentalDecorators: false,
flow: false,
},
})

it("should ignore non vue files in extractor", async () => {
const match = vueExtractor.match("test.js")

expect(match).toBeFalsy()
})

it("should extract message from vue file", async () => {
const filePath = path.resolve(__dirname, "fixtures/test.vue")
const code = fs.readFileSync(filePath, "utf-8")

let messages: ExtractedMessage[] = []

await vueExtractor.extract(
"test.vue",
code,
(res) => {
messages.push(res)
},
{
linguiConfig,
}
)

messages = normalizePath(messages)

expect(messages).toMatchSnapshot()
})
})
37 changes: 37 additions & 0 deletions packages/extractor-vue/src/fixtures/test.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
import { i18n } from "@lingui/core"

i18n._("Setup message")
let x: string | number = 1
</script>

<script lang="ts">
import { defineComponent } from "vue"
import { i18n } from "@lingui/core"

// @ts-ignore only used to check if extractor doesn't crash with Typescript
const foo: number = 5

export default defineComponent({
data() {
return {
i18n,
scriptString: i18n.t("Script message"),
}
},
})
</script>

<template>
{{ (x as number).toFixed(2) }}
{{ i18n.t({ id: "custom.id", message: "My message" }) }}
{{
i18n.t({
message: "My descriptor message",
id: "my.message",
comment: "Message comment",
})
}}
{{ i18n._("id used as message") }}
{{ i18n.t({ id: "My message without ID and context" }) }}
</template>
1 change: 1 addition & 0 deletions packages/extractor-vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { vueExtractor } from "./vue-extractor"
64 changes: 64 additions & 0 deletions packages/extractor-vue/src/vue-extractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { parse, compileTemplate, SFCBlock } from "@vue/compiler-sfc"
import babel from "@lingui/cli/api/extractors/babel"
import type { ExtractorCtx, ExtractorType } from "@lingui/conf"

export const vueExtractor: ExtractorType = {
match(filename: string) {
return filename.endsWith(".vue")
},
async extract(
filename: string,
code: string,
onMessageExtracted,
ctx: ExtractorCtx
) {
const { descriptor } = parse(code, {
sourceMap: true,
filename,
ignoreEmpty: true,
})

const compiledTemplate = compileTemplate({
source: descriptor.template.content,
filename,
inMap: descriptor.template.map,
id: filename,
})

const isTsBlock = (block: SFCBlock) => block?.lang === "ts"

const targets = [
[
descriptor.script?.content,
descriptor.script?.map,
isTsBlock(descriptor.script),
],
[
descriptor.scriptSetup?.content,
descriptor.scriptSetup?.map,
isTsBlock(descriptor.scriptSetup),
],
[
compiledTemplate?.code,
compiledTemplate?.map,
isTsBlock(descriptor.script) || isTsBlock(descriptor.scriptSetup),
],
] as const

await Promise.all(
targets
.filter(([source]) => Boolean(source))
.map(([source, map, isTs]) =>
babel.extract(
filename + (isTs ? ".ts" : ""),
source,
onMessageExtracted,
{
sourceMaps: map,
...ctx,
}
)
)
)
},
}
10 changes: 8 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"skipLibCheck": true,
"target": "es2017",
"paths": {
"@lingui/babel-plugin-extract-messages": ["./packages/babel-plugin-extract-messages/src"],
"@lingui/babel-plugin-extract-messages": [
"./packages/babel-plugin-extract-messages/src"
],
"@lingui/core": ["./packages/core/src"],
"@lingui/message-utils/*": ["./packages/message-utils/src/*"],
"@lingui/cli/api": ["./packages/cli/src/api"],
Expand All @@ -21,7 +23,11 @@
"@lingui/macro/node": ["./packages/macro/src/index.ts"],
"@lingui/macro": ["./packages/macro/src"],
"@lingui/format-po": ["./packages/format-po/src/po.ts"],
"@lingui/format-json": ["./packages/format-json/src/json.ts"]
"@lingui/format-json": ["./packages/format-json/src/json.ts"],
"@lingui/extractor-vue": ["./packages/extractor-vue/src"],
"@lingui/cli/api/extractors/babel": [
"./packages/cli/src/api/extractors/babel"
]
}
},
"exclude": [
Expand Down
Loading