Skip to content
Open
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,23 @@ Typescript is supported via Javascript and Jsx lexers. If you are using Javascri
}
```

#### Vue

```js
{
// Vue needs to parse its syntax through both VueTemplateLexer
// This lexer supports HTMLLexer and JavascriptLexer options
vue: [
{
lexer: 'VueTemplateLexer',
vueBindAttr: true, // enable vue template syntax bind attribute
functions: ['t', '$t'], // Array of functions to match
namespaceFunctions: ['useTranslation', 'withTranslation'],
},
]
}
```

#### Custom lexers

You can provide function instead of string as a custom lexer.
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { default as HandlebarsLexer } from './lexers/handlebars-lexer.js'
export { default as HTMLLexer } from './lexers/html-lexer.js'
export { default as JavascriptLexer } from './lexers/javascript-lexer.js'
export { default as JsxLexer } from './lexers/jsx-lexer.js'
export { default as VueTemplateLexer } from './lexers/vue-template-lexer.js'
49 changes: 49 additions & 0 deletions src/lexers/vue-template-lexer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* @Author: Bin
* @Date: 2025-01-04
* @FilePath: /i18next-parser/src/lexers/vue-template-lexer.js
*/
import HTMLLexer from './html-lexer.js'
import JavascriptLexer from './javascript-lexer.js'
import * as cheerio from 'cheerio'

export default class VueTemplateLexer extends HTMLLexer {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why are you extending the HTMLLexer and then compose the jsLexer as well. For better maintainability, shouldn't it be extending from the BaseLexer and compose both the JSLexer and the HTMLLexer?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geertklop Yes, by expanding BaseLexer, you can theoretically get better maintainability, but you will eventually find that it is actually a re-realization of HTMLLexer and JSLexer (excellent template syntax 🐶)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that with the maintainability, I would expect to extend from BaseLexer. However, curious what regular maintainers like @karellm think.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geertklop If you want, you can complete the follow-up work based on my branch

constructor(options = {}) {
super(options)
this.vueBindAttr = options.vueBindAttr || true
this.jsLexer = new JavascriptLexer(options)
}

extract(content, filename = '__default.vue') {
const $ = cheerio.load(content)
const jsLexer = this.jsLexer
let keys = []

// use HTMLLexer
const HTMLkeys = super.extract(content)
keys.push(...HTMLkeys)

// use JavascriptLexer
const JSKeys = jsLexer.extract(content, filename)
keys.push(...JSKeys)

// support vue bind attribute
if (this.vueBindAttr) {
// traverse all tags to filter bind attribute
$('*').each((_, node) => {
const attributes = node.attributes.filter(
(item) => item.name.startsWith(':') || item.name.startsWith('v-bind:')
)
if (attributes.length > 0) {
// there are computed properties, it may have simple javascript logic
attributes.forEach((content) => {
const items = jsLexer.extract(content.value, filename)
keys.push(...items)
})
}
})
}

return keys
}
}
4 changes: 3 additions & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HandlebarsLexer from './lexers/handlebars-lexer.js'
import HTMLLexer from './lexers/html-lexer.js'
import JavascriptLexer from './lexers/javascript-lexer.js'
import JsxLexer from './lexers/jsx-lexer.js'
import VueTemplateLexer from './lexers/vue-template-lexer.js'

const lexers = {
hbs: ['HandlebarsLexer'],
Expand All @@ -18,7 +19,7 @@ const lexers = {
jsx: ['JsxLexer'],
tsx: ['JsxLexer'],

vue: ['JavascriptLexer'],
vue: ['VueTemplateLexer'],

default: ['JavascriptLexer'],
}
Expand All @@ -28,6 +29,7 @@ const lexersMap = {
HTMLLexer,
JavascriptLexer,
JsxLexer,
VueTemplateLexer,
}

export default class Parser extends EventEmitter {
Expand Down
31 changes: 31 additions & 0 deletions test/lexers/vue-template-lexer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* @Author: Bin
* @Date: 2025-01-04
* @FilePath: /i18next-parser/test/lexers/vue-template-lexer.test.js
*/
import { assert } from 'chai'
import VueTemplateLexer from '../../src/lexers/vue-template-lexer.js'

describe('VueTemplateLexer', () => {
it('supports vue bind attributes', (done) => {
const Lexer = new VueTemplateLexer({
functions: ['t', '$t'],
vueBindAttr: true,
})
const content = `
<template>
<button :aria-label="t('b_a_l', 'button aria label')">
button label
</button>
<button v-bind:aria-label="$t('button v-bind aria label')">
button label form v-bind
</button>
</template>
`
assert.deepEqual(Lexer.extract(content), [
{ key: 'b_a_l', defaultValue: 'button aria label' },
{ key: 'button v-bind aria label' },
])
done()
})
})