Skip to content

Commit

Permalink
Add support for Vue3 plugin & update CLI
Browse files Browse the repository at this point in the history
In order to support Vue3 we had to drop `vue-template-compiler`
in favor of `@vue/compiler-sfc` for compatibility reasons. This
way we can now support both Vue2 and Vue3.

Side effects
`vue/compiler-sfc` ast result is more cryptic than the previous
dependency, so we might need to revisit in the future and
use an alternative.
  • Loading branch information
codegaze committed Nov 29, 2022
1 parent 5a8fa64 commit b234050
Show file tree
Hide file tree
Showing 17 changed files with 627 additions and 18 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ Transifex Native support for localizing Angular components.

## Transifex Native for Vue

Transifex Native support for localizing Vue components.
### Transifex Native support for localizing Vue2 components.
[Read more](https://github.com/transifex/transifex-javascript/tree/master/packages/vue2)

### Transifex Native support for localizing Vue3 components.
[Read more](https://github.com/transifex/transifex-javascript/tree/master/packages/vue3)

## Transifex Native for ExpressJS

Transifex Native support for localizing ExpressJS applications.
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
"@colors/colors": "^1.5.0",
"@oclif/core": "^1.20.4",
"@transifex/native": "^4.3.0",
"@vue/compiler-sfc": "^3.2.45",
"angular-html-parser": "^1.8.0",
"axios": "^1.1.3",
"ejs": "^3.1.8",
"glob": "^8.0.3",
"lodash": "^4.17.21",
"pug": "^3.0.2",
"shelljs": "^0.8.5",
"vue-template-compiler": "^2.7.14"
"shelljs": "^0.8.5"
},
"devDependencies": {
"@oclif/dev-cli": "^1.26.10",
Expand Down
33 changes: 18 additions & 15 deletions packages/cli/src/api/parsers/vue.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const _ = require('lodash');
const vueTemplateCompiler = require('vue-template-compiler');
const vueTemplateCompiler = require('@vue/compiler-sfc');
const { createPayload, isPayloadValid } = require('./utils');
const { mergePayload } = require('../merge');
const { babelExtractPhrases } = require('./babel');

/**
* A function to traverse the AST provided by the vue-template-compiler package
* A function to traverse the AST provided by the vue/compiler-sfc package
*
* @param {*} ast
* @param {*} visitor
Expand All @@ -14,7 +14,7 @@ function traverseVueTemplateAst(ast, visitor = {}) {
// Use this in order to identify expressions in the AST since there is no
// Tag to identify it with when traversing
const VISITORS = {
2: 'Expression',
5: 'Expression',
};

function traverseArray(array, parent) {
Expand All @@ -28,10 +28,13 @@ function traverseVueTemplateAst(ast, visitor = {}) {
if (visitor.enter) visitor.enter(node, parent);
if (visitor[node.tag]) visitor[node.tag](node, parent);
// Take care of expressions
if (visitor[VISITORS[node.type]] && node.expression) {
visitor[VISITORS[node.type]](node, parent);
if (visitor[VISITORS[node.type]] && node.content) {
if (node.content.loc.source) visitor[VISITORS[node.type]](node, parent);
}
if (node.children) traverseArray(node.children, node);
if (node.content) {
if (node.content.children) traverseArray(node.content.children, node);
}
if (visitor.exit) visitor.exit(node, parent);
}
traverseNode(ast, null);
Expand All @@ -54,11 +57,11 @@ function vueElementVisitor(HASHES, relativeFile, options) {
return (node) => {
let string;
const params = {};
_.each(node.attrsList, (attr) => {
_.each(node.props, (attr) => {
const property = attr.name;
if (!property || !attr.value) return;

const attrValue = attr.value;
const attrValue = attr.value.content;

if (!attrValue) return;

Expand Down Expand Up @@ -102,25 +105,25 @@ function vueElementVisitor(HASHES, relativeFile, options) {
*/
function extractVuePhrases(HASHES, source, relativeFile, options) {
// Use the vue-template-compiler API to parse content
const vueContent = vueTemplateCompiler.parseComponent(source);

const vueContent = vueTemplateCompiler.parse(source);
// Get the JS Content from the file and extract hashes/phrases with Babel
if (vueContent.script && vueContent.script.content) {
const script = vueContent.script.content;
if (vueContent.descriptor.script && vueContent.descriptor.script.content) {
const script = vueContent.descriptor.script.content;
babelExtractPhrases(HASHES, script, relativeFile, options);
}

// Get the template content from the file and extract hashes/phrases with
// custom traverse function
if (vueContent.template && vueContent.template.content) {
if (vueContent.descriptor.template && vueContent.descriptor.template.content) {
// Compile to get the AST
const template = vueTemplateCompiler.compile(vueContent.template.content, {
preserveWhitespace: false,
const template = vueTemplateCompiler.compileTemplate({
id: 'sfc-compiler',
source: vueContent.descriptor.template.content,
});

traverseVueTemplateAst(template.ast, {
Expression(node) {
babelExtractPhrases(HASHES, node.expression, relativeFile, options);
babelExtractPhrases(HASHES, node.content.loc.source, relativeFile, options);
},
T: vueElementVisitor(HASHES, relativeFile, options),
UT: vueElementVisitor(HASHES, relativeFile, options),
Expand Down
7 changes: 7 additions & 0 deletions packages/vue3/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": ["airbnb-base"],
"rules": {
"no-underscore-dangle": "off"
},
"ignorePatterns": ["dist/"]
}
4 changes: 4 additions & 0 deletions packages/vue3/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tests
.eslintrc.yml
coverage
tags
205 changes: 205 additions & 0 deletions packages/vue3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
<p align="center">
<a href="https://www.transifex.com">
<img src="https://raw.githubusercontent.com/transifex/transifex-javascript/master/media/transifex.png" height="60">
</a>
</p>
<p align="center">
<i>Transifex Native is a full end-to-end, cloud-based localization stack for moderns apps.</i>
</p>
<p align="center">
<img src="https://github.com/transifex/transifex-javascript/actions/workflows/npm-publish.yml/badge.svg">
<a href="https://www.npmjs.com/package/@transifex/vue3">
<img src="https://img.shields.io/npm/v/@transifex/vue3.svg">
</a>
<a href="https://developers.transifex.com/docs/native">
<img src="https://img.shields.io/badge/docs-transifex.com-blue">
</a>
</p>

# Transifex Native SDK: Vue i18n

Vue3 component for localizing Vue application using
[Transifex Native](https://www.transifex.com/native/).

Related packages:
- [@transifex/native](https://www.npmjs.com/package/@transifex/native)
- [@transifex/cli](https://www.npmjs.com/package/@transifex/cli)

Learn more about Transifex Native in the [Transifex Developer Hub](https://developers.transifex.com/docs/native).

# How it works

**Step1**: Create a Transifex Native project in [Transifex](https://www.transifex.com).

**Step2**: Grab credentials.

**Step3**: Internationalize the code using the SDK.

**Step4**: Push source phrases using the `@transifex/cli` tool.

**Step5**: Translate the app using over-the-air updates.

No translation files required.

![native](https://raw.githubusercontent.com/transifex/transifex-javascript/master/media/native.gif)

# Upgrade to v2

If you are upgrading from the `1.x.x` version, please read this [migration guide](https://github.com/transifex/transifex-javascript/blob/HEAD/UPGRADE_TO_V2.md), as there are breaking changes in place.


# Install

Install the library and its dependencies using:

```sh
npm install @transifex/native @transifex/vue3 --save
```

# Usage

## Initiate the plugin in a Vue App

```javascript
import { createApp } from 'vue';
import App from './App.vue';
import { tx } from '@transifex/native';
import { TransifexVue } from '@transifex/vue3';

tx.init({
token: '<token>',
});

const app = createApp(App);

app.use(TransifexVue);
app.mount('#app');

```


## `T` Component

```javascript
<template>
<div>
<T _str="Hello world" />
<T _str="Hello {username}" :username="user" />
</div>
</template>

<script>
export default {
name: 'App',
data() {
return {
user: 'John'
};
},
}
</script>
```

Available optional props:

| Prop | Type | Description |
|------------|--------|---------------------------------------------|
| _context | String | String context, affects key generation |
| _key | String | Custom string key |
| _comment | String | Developer comment |
| _charlimit | Number | Character limit instruction for translators |
| _tags | String | Comma separated list of tags |


## `UT` Component

```javascript
<template>
<div>
<UT _str="Hello <b>{username}</b>" :username="user" />
<p>
<UT _str="Hello <b>{username}</b>" :username="user" _inline="true" />
</p>
</div>
</template>

<script>
export default {
name: 'App',
data() {
return {
user: 'John'
};
},
}
</script>
```

`UT` has the same behaviour as `T`, but renders source string as HTML inside a
`div` tag.

Available optional props: All the options of `T` plus:

| Prop | Type | Description |
|---------|---------|-------------------------------------------------|
| _inline | Boolean | Wrap translation in `span` |

## `$t` template function or `this.t` alias for scripts

Makes the current component re-render when a language change is detected and
returns a t-function you can use to translate strings programmatically.

You will most likely prefer to use the `T` or `UT` components over this, unless
for some reason you want to have the translation output in a variable for
manipulation.

```javascript
<template>
<div>
{{$t('Hello world').toLowerCase()}}
{{hellofromscript}}
</div>
</template>

<script>
export default {
name: 'App',
computed: {
hellofromscript: function() { return this.t('Hello world').toLowerCase() },
},
}
</script>

```

## `LanguagePicker` component

Renders a `<select>` tag that displays supported languages and switches the
application's selected language on change.

```javascript
<template>
<div>
<T _str="This is a translatable message" />
<LanguagePicker />
</div>
</template>

<script>
import { LanguagePicker } from '@transifex/vue3';
export default {
name: 'App',
components: {
LanguagePicker,
}
}
</script>
```

Accepts properties:

- `className`: The CSS class that will be applied to the `<select>` tag.

# License

Licensed under Apache License 2.0, see [LICENSE](https://github.com/transifex/transifex-javascript/blob/HEAD/LICENSE) file.
3 changes: 3 additions & 0 deletions packages/vue3/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
};
Loading

0 comments on commit b234050

Please sign in to comment.