mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-01-18 05:10:05 -06:00
feat(ui): plugin locales
This commit is contained in:
@@ -10,6 +10,7 @@ This guide will walk you through the development of cli-ui specific features for
|
||||
- [Shared data](#shared-data)
|
||||
- [Plugin actions](#plugin-actions)
|
||||
- [Inter-process communication (IPC)](#inter-process-communication-ipc)
|
||||
- [Localization](#localization)
|
||||
- [Hooks](#hooks)
|
||||
- [Public static files](#public-static-files)
|
||||
|
||||
@@ -350,11 +351,19 @@ ClientAddonApi.component('vue-webpack-dashboard', WebpackDashboard)
|
||||
ClientAddonApi.addRoutes('vue-webpack', [
|
||||
{ path: '', name: 'test-webpack-route', component: TestView }
|
||||
])
|
||||
|
||||
// You can translate your plugin components
|
||||
// Load the locale files (uses vue-i18n)
|
||||
const locales = require.context('./locales', true, /[a-z0-9]+\.json$/i)
|
||||
locales.keys().forEach(key => {
|
||||
const locale = key.match(/([a-z0-9]+)\./i)[1]
|
||||
ClientAddonApi.addLocalization(locale, locales(key))
|
||||
})
|
||||
```
|
||||
|
||||
The cli-ui registers `Vue` and `ClientAddonApi` as global variables in the `window` scope.
|
||||
|
||||
In your components, you can use all the components and the CSS classes of [@vue/ui](https://github.com/vuejs/ui) and [@vue/cli-ui](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-ui/src/components) in order to keep the look and feel consistent.
|
||||
In your components, you can use all the components and the CSS classes of [@vue/ui](https://github.com/vuejs/ui) and [@vue/cli-ui](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-ui/src/components) in order to keep the look and feel consistent. You can also translate the strings with [vue-i18n](https://github.com/kazupon/vue-i18n) which is included.
|
||||
#### Register the client addon
|
||||
|
||||
Back to the `ui.js` file, use the `api.addClientAddon` method with a require query to the built folder:
|
||||
@@ -632,6 +641,43 @@ api.ipcSend({
|
||||
})
|
||||
```
|
||||
|
||||
### Localization
|
||||
|
||||
You can put locale files compatible with [vue-i18n](https://github.com/kazupon/vue-i18n) in a `locales` folder at the root of your plugin. They will be automatically loaded into the client when the project is opened. You can then use `$t` to translate strings in your components and other vue-i18n helpers. Also, the strings used in the UI API (like `describeTask`) will go through vue-i18n as well to you can localize them.
|
||||
|
||||
Example `locales` folder:
|
||||
|
||||
```
|
||||
vue-cli-plugin/locales/en.json
|
||||
vue-cli-plugin/locales/fr.json
|
||||
```
|
||||
|
||||
Example usage in API:
|
||||
|
||||
```js
|
||||
api.describeConfig({
|
||||
// vue-i18n path
|
||||
description: 'my-plugin.config.foo'
|
||||
})
|
||||
```
|
||||
|
||||
Example usage in components:
|
||||
|
||||
```html
|
||||
<VueButton>{{ $t('my-plugin.actions.bar') }}</VueButton>
|
||||
```
|
||||
|
||||
You can also load the locale files in a client addon if you prefer, using the `ClientAddonApi`:
|
||||
|
||||
```js
|
||||
// Load the locale files (uses vue-i18n)
|
||||
const locales = require.context('./locales', true, /[a-z0-9]+\.json$/i)
|
||||
locales.keys().forEach(key => {
|
||||
const locale = key.match(/([a-z0-9]+)\./i)[1]
|
||||
ClientAddonApi.addLocalization(locale, locales(key))
|
||||
})
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks allows to react to certain cli-ui events.
|
||||
|
||||
13
packages/@vue/cli-plugin-eslint/locales/en.json
Normal file
13
packages/@vue/cli-plugin-eslint/locales/en.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"eslint": {
|
||||
"config": {
|
||||
"eslint": {
|
||||
"description": "Error checking & Code quality",
|
||||
"groups": {
|
||||
"strongly-recommended": "Strongly recommended",
|
||||
"recommended": "Recommended"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
packages/@vue/cli-plugin-eslint/locales/fr.json
Normal file
13
packages/@vue/cli-plugin-eslint/locales/fr.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"eslint": {
|
||||
"config": {
|
||||
"eslint": {
|
||||
"description": "Vérification des erreurs & Qualité du code",
|
||||
"groups": {
|
||||
"strongly-recommended": "Fortement recommandé",
|
||||
"recommended": "Recommandé"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ module.exports = api => {
|
||||
api.describeConfig({
|
||||
id: 'eslintrc',
|
||||
name: 'ESLint configuration',
|
||||
description: 'Error checking & Code quality',
|
||||
description: 'eslint.config.eslint.description',
|
||||
link: 'https://eslint.org',
|
||||
icon: '.eslintrc.json',
|
||||
files: {
|
||||
@@ -19,7 +19,7 @@ module.exports = api => {
|
||||
name: 'vue/attribute-hyphenation',
|
||||
type: 'list',
|
||||
message: 'Attribute hyphenation',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'Enforce attribute naming style in template (`my-prop` or `myProp`)',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md',
|
||||
default: JSON.stringify('off'),
|
||||
@@ -43,7 +43,7 @@ module.exports = api => {
|
||||
name: 'vue/html-end-tags',
|
||||
type: 'confirm',
|
||||
message: 'Template end tags style',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'End tag on Void elements, end tags and self-closing opening tags',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-end-tags.md',
|
||||
default: false,
|
||||
@@ -55,7 +55,7 @@ module.exports = api => {
|
||||
name: 'vue/html-indent',
|
||||
type: 'list',
|
||||
message: 'Template indentation',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'Enforce indentation in template',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-indent.md',
|
||||
default: JSON.stringify('off'),
|
||||
@@ -87,7 +87,7 @@ module.exports = api => {
|
||||
name: 'vue/html-self-closing',
|
||||
type: 'confirm',
|
||||
message: 'Template tag self-closing style',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'Self-close any component or non-Void element tags',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-self-closing.md',
|
||||
default: false,
|
||||
@@ -99,7 +99,7 @@ module.exports = api => {
|
||||
name: 'vue/require-default-prop',
|
||||
type: 'confirm',
|
||||
message: 'Require default in required props',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'This rule requires default value to be set for each props that are not marked as `required`',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/require-default-prop.md',
|
||||
default: false,
|
||||
@@ -111,7 +111,7 @@ module.exports = api => {
|
||||
name: 'vue/require-prop-types',
|
||||
type: 'confirm',
|
||||
message: 'Require types for props',
|
||||
group: 'Strongly recommended',
|
||||
group: 'eslint.config.eslint.groups.strongly-recommended',
|
||||
description: 'In committed code, prop definitions should always be as detailed as possible, specifying at least type(s)',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/require-prop-types.md',
|
||||
default: false,
|
||||
@@ -123,7 +123,7 @@ module.exports = api => {
|
||||
name: 'vue/attributes-order',
|
||||
type: 'confirm',
|
||||
message: 'Attribute order',
|
||||
group: 'Recommended',
|
||||
group: 'eslint.config.eslint.groups.recommended',
|
||||
description: 'This rule aims to enforce ordering of component attributes (the default order is specified in the Vue style guide)',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attributes-order.md',
|
||||
default: false,
|
||||
@@ -135,7 +135,7 @@ module.exports = api => {
|
||||
name: 'vue/html-quotes',
|
||||
type: 'list',
|
||||
message: 'Attribute quote style',
|
||||
group: 'Recommended',
|
||||
group: 'eslint.config.eslint.groups.recommended',
|
||||
description: 'Enforce style of the attribute quotes in templates',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-quotes.md',
|
||||
default: JSON.stringify('off'),
|
||||
@@ -159,7 +159,7 @@ module.exports = api => {
|
||||
name: 'vue/order-in-components',
|
||||
type: 'confirm',
|
||||
message: 'Component options order',
|
||||
group: 'Recommended',
|
||||
group: 'eslint.config.eslint.groups.recommended',
|
||||
description: 'This rule aims to enforce ordering of component options (the default order is specified in the Vue style guide)',
|
||||
link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/order-in-components.md',
|
||||
default: false,
|
||||
|
||||
18
packages/@vue/cli-service/locales/en.json
Normal file
18
packages/@vue/cli-service/locales/en.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"vue-webpack": {
|
||||
"dashboard": {
|
||||
"title": "Dashboard"
|
||||
},
|
||||
"analyzer": {
|
||||
"title": "Analyzer"
|
||||
},
|
||||
"tasks": {
|
||||
"serve": {
|
||||
"description": "Compiles and hot-reloads for development"
|
||||
},
|
||||
"build": {
|
||||
"description": "Compiles and minifies for production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
packages/@vue/cli-service/locales/fr.json
Normal file
18
packages/@vue/cli-service/locales/fr.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"vue-webpack": {
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord"
|
||||
},
|
||||
"analyzer": {
|
||||
"title": "Analyseur"
|
||||
},
|
||||
"tasks": {
|
||||
"serve": {
|
||||
"description": "Compile et recharge à chaud pour le développement"
|
||||
},
|
||||
"build": {
|
||||
"description": "Compile et minifie pour la production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,13 @@ module.exports = api => {
|
||||
views: [
|
||||
{
|
||||
id: 'vue-webpack-dashboard',
|
||||
label: 'Dashboard',
|
||||
label: 'vue-webpack.dashboard.title',
|
||||
icon: 'dashboard',
|
||||
component: 'vue-webpack-dashboard'
|
||||
},
|
||||
{
|
||||
id: 'vue-webpack-analyzer',
|
||||
label: 'Analyzer',
|
||||
label: 'vue-webpack.analyzer.title',
|
||||
icon: 'donut_large',
|
||||
component: 'vue-webpack-analyzer'
|
||||
}
|
||||
@@ -49,7 +49,7 @@ module.exports = api => {
|
||||
}
|
||||
api.describeTask({
|
||||
match: /vue-cli-service serve/,
|
||||
description: 'Compiles and hot-reloads for development',
|
||||
description: 'vue-webpack.tasks.serve.description',
|
||||
link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve',
|
||||
prompts: [
|
||||
{
|
||||
@@ -121,7 +121,7 @@ module.exports = api => {
|
||||
})
|
||||
api.describeTask({
|
||||
match: /vue-cli-service build/,
|
||||
description: 'Compiles and minifies for production',
|
||||
description: 'vue-webpack.tasks.build.description',
|
||||
link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#build',
|
||||
prompts: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="asset-list list-block">
|
||||
<div class="content">
|
||||
<div class="title">Assets</div>
|
||||
<div class="title">
|
||||
{{ $t('vue-webpack.dashboard.asset-list.title') }}
|
||||
</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!assetsSorted.length"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
v-if="!asset.secondary && asset.big"
|
||||
icon="warning"
|
||||
class="icon"
|
||||
v-tooltip="'This asset is big, consider using Code splitting to create smaller assets.'"
|
||||
v-tooltip="$t('vue-webpack.dashboard.asset-list.size-warning')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,33 +2,49 @@
|
||||
<div class="build-status">
|
||||
<div class="content">
|
||||
<div class="info-block status">
|
||||
<div class="label">Status</div>
|
||||
<div class="value">{{ status || 'Idle' }}</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.status') }}
|
||||
</div>
|
||||
<div class="value">{{ $t(`vue-webpack.dashboard.webpack-status.${status || 'Idle'}`) }}</div>
|
||||
</div>
|
||||
<div class="info-block errors">
|
||||
<div class="label">Errors</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.errors') }}
|
||||
</div>
|
||||
<div class="value">{{ errors.length }}</div>
|
||||
</div>
|
||||
<div class="info-block warnings">
|
||||
<div class="label">Warnings</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.warnings') }}
|
||||
</div>
|
||||
<div class="value">{{ warnings.length }}</div>
|
||||
</div>
|
||||
<div class="info-block assets">
|
||||
<div class="label">Assets</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.assets') }}
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ assetsTotalSize | size('B') }}
|
||||
<span class="secondary">({{ sizeField }})</span>
|
||||
<span class="secondary">
|
||||
({{ $t(`vue-webpack.sizes.${sizeField}`) }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-block modules">
|
||||
<div class="label">Modules</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.modules') }}
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ modulesTotalSize | size('B') }}
|
||||
<span class="secondary">({{ sizeField }})</span>
|
||||
<span class="secondary">
|
||||
({{ $t(`vue-webpack.sizes.${sizeField}`) }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-block dep-modules">
|
||||
<div class="label">Dependencies</div>
|
||||
<div class="label">
|
||||
{{ $t('vue-webpack.dashboard.build-status.labels.deps') }}
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ depModulesTotalSize | size('B') }}
|
||||
<span class="secondary">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="module-list list-block">
|
||||
<div class="content">
|
||||
<div class="title">Dependencies</div>
|
||||
<div class="title">
|
||||
{{ $t('vue-webpack.dashboard.module-list.title') }}
|
||||
</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!depModules.length"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="speed-stats">
|
||||
<div class="content">
|
||||
<div class="title">Speed stats</div>
|
||||
<div class="title">
|
||||
{{ $t('vue-webpack.dashboard.speed-stats.title') }}
|
||||
</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!assetsTotalSize"
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
<div class="vue-webpack-analyzer">
|
||||
<div class="pane-toolbar">
|
||||
<VueIcon icon="donut_large"/>
|
||||
<div class="title">Analyzer</div>
|
||||
<div class="title">{{ $t('vue-webpack.analyzer.title') }}</div>
|
||||
|
||||
<template v-if="currentTree">
|
||||
<VueButton
|
||||
icon-left="arrow_upward"
|
||||
label="Go up"
|
||||
:label="$t('vue-webpack.analyzer.go-up')"
|
||||
:disabled="currentTree === rootTree"
|
||||
@click="goToParent()"
|
||||
/>
|
||||
<VueButton
|
||||
icon-left="home"
|
||||
label="Go to home"
|
||||
:label="$t('vue-webpack.analyzer.go-home')"
|
||||
:disabled="currentTree === rootTree"
|
||||
@click="goToHome()"
|
||||
/>
|
||||
@@ -22,23 +22,26 @@
|
||||
class="separator"
|
||||
/>
|
||||
</template>
|
||||
<VueSelect v-model="selectedChunk">
|
||||
<VueSelect
|
||||
v-model="selectedChunk"
|
||||
:disabled="Object.keys(modulesTrees).length === 0"
|
||||
>
|
||||
<VueSelectButton
|
||||
v-for="(chunk, key) of modulesTrees"
|
||||
:key="key"
|
||||
:value="key"
|
||||
:label="`Chunk ${getChunkName(key)}`"
|
||||
:label="`${$t('vue-webpack.analyzer.chunk')} ${getChunkName(key)}`"
|
||||
/>
|
||||
</VueSelect>
|
||||
<VueSelect v-model="sizeField">
|
||||
<VueSelectButton value="stats" label="Stats sizes"/>
|
||||
<VueSelectButton value="parsed" label="Parsed sizes"/>
|
||||
<VueSelectButton value="gzip" label="Gzip sizes"/>
|
||||
<VueSelectButton value="stats" :label="`${$t('vue-webpack.sizes.stats')}`"/>
|
||||
<VueSelectButton value="parsed" :label="`${$t('vue-webpack.sizes.parsed')}`"/>
|
||||
<VueSelectButton value="gzip" :label="`${$t('vue-webpack.sizes.gzip')}`"/>
|
||||
</VueSelect>
|
||||
<VueButton
|
||||
class="icon-button"
|
||||
icon-left="help"
|
||||
v-tooltip="sizeHelp"
|
||||
v-tooltip="$t('vue-webpack.sizes.help')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -76,19 +79,19 @@
|
||||
class="stats size"
|
||||
:class="{ selected: sizeField === 'stats' }"
|
||||
>
|
||||
Stats: {{ describedModule.size.stats | size('B')}}
|
||||
{{ $t('vue-webpack.sizes.stats') }}: {{ describedModule.size.stats | size('B')}}
|
||||
</div>
|
||||
<div
|
||||
class="parsed size"
|
||||
:class="{ selected: sizeField === 'parsed' }"
|
||||
>
|
||||
Parsed: {{ describedModule.size.parsed | size('B')}}
|
||||
{{ $t('vue-webpack.sizes.parsed') }}: {{ describedModule.size.parsed | size('B')}}
|
||||
</div>
|
||||
<div
|
||||
class="gzip size"
|
||||
:class="{ selected: sizeField === 'gzip' }"
|
||||
>
|
||||
Gzip: {{ describedModule.size.gzip | size('B')}}
|
||||
{{ $t('vue-webpack.sizes.gzip') }}: {{ describedModule.size.gzip | size('B')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<div class="vue-webpack-dashboard">
|
||||
<div class="pane-toolbar">
|
||||
<VueIcon icon="dashboard"/>
|
||||
<div class="title">Dashboard</div>
|
||||
<div class="title">{{ $t('vue-webpack.dashboard.title') }}</div>
|
||||
|
||||
<template
|
||||
v-if="mode === 'serve'"
|
||||
>
|
||||
<VueButton
|
||||
icon-left="open_in_browser"
|
||||
label="Open app"
|
||||
:label="$t('vue-webpack.dashboard.open-app')"
|
||||
:disabled="!serveUrl"
|
||||
@click="$callPluginAction('webpack-dashboard-open-app')"
|
||||
/>
|
||||
@@ -19,14 +19,14 @@
|
||||
/>
|
||||
</template>
|
||||
<VueSelect v-model="sizeField">
|
||||
<VueSelectButton value="stats" label="Stats sizes"/>
|
||||
<VueSelectButton value="parsed" label="Parsed sizes"/>
|
||||
<VueSelectButton value="gzip" label="Gzip sizes"/>
|
||||
<VueSelectButton value="stats" :label="`${$t('vue-webpack.sizes.stats')}`"/>
|
||||
<VueSelectButton value="parsed" :label="`${$t('vue-webpack.sizes.parsed')}`"/>
|
||||
<VueSelectButton value="gzip" :label="`${$t('vue-webpack.sizes.gzip')}`"/>
|
||||
</VueSelect>
|
||||
<VueButton
|
||||
class="icon-button"
|
||||
icon-left="help"
|
||||
v-tooltip="sizeHelp"
|
||||
v-tooltip="$t('vue-webpack.sizes.help')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
45
packages/@vue/cli-ui-addon-webpack/src/locales/en.json
Normal file
45
packages/@vue/cli-ui-addon-webpack/src/locales/en.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"vue-webpack": {
|
||||
"dashboard": {
|
||||
"open-app": "Open app",
|
||||
"webpack-status": {
|
||||
"Success": "Success",
|
||||
"Failed": "Failed",
|
||||
"Compiling": "Compiling",
|
||||
"Invalidated": "Invalidated",
|
||||
"Idle": "Idle"
|
||||
},
|
||||
"build-status": {
|
||||
"labels": {
|
||||
"status": "Status",
|
||||
"errors": "Errors",
|
||||
"warnings": "Warnings",
|
||||
"assets": "Assets",
|
||||
"modules": "Modules",
|
||||
"deps": "Dependencies"
|
||||
}
|
||||
},
|
||||
"speed-stats": {
|
||||
"title": "Speed stats"
|
||||
},
|
||||
"module-list": {
|
||||
"title": "Dependencies"
|
||||
},
|
||||
"asset-list": {
|
||||
"title": "Assets",
|
||||
"size-warning": "This asset is big, consider using Code splitting to create smaller assets."
|
||||
}
|
||||
},
|
||||
"analyzer": {
|
||||
"go-up": "Go up",
|
||||
"go-home": "Go to home",
|
||||
"chunk": "Chunk"
|
||||
},
|
||||
"sizes": {
|
||||
"stats": "Stats",
|
||||
"parsed": "Parsed",
|
||||
"gzip": "Gzip",
|
||||
"help": "<b>Stats:</b> size from webpack stats data.<br><b>Parsed:</b> size from extracted source (after minification plugins). More accurate.<br><b>Gzip:</b> size of gzipped extracted source."
|
||||
}
|
||||
}
|
||||
}
|
||||
45
packages/@vue/cli-ui-addon-webpack/src/locales/fr.json
Normal file
45
packages/@vue/cli-ui-addon-webpack/src/locales/fr.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"vue-webpack": {
|
||||
"dashboard": {
|
||||
"open-app": "Ouvrir l'app",
|
||||
"webpack-status": {
|
||||
"Success": "Succès",
|
||||
"Failed": "Echec",
|
||||
"Compiling": "Compilation...",
|
||||
"Invalidated": "Invalidé",
|
||||
"Idle": "Inoccupé"
|
||||
},
|
||||
"build-status": {
|
||||
"labels": {
|
||||
"status": "Statut",
|
||||
"errors": "Erreurs",
|
||||
"warnings": "Avertissements",
|
||||
"assets": "Fichiers",
|
||||
"modules": "Modules",
|
||||
"deps": "Dépendances"
|
||||
}
|
||||
},
|
||||
"speed-stats": {
|
||||
"title": "Statistiques de vitesse"
|
||||
},
|
||||
"module-list": {
|
||||
"title": "Dépendances"
|
||||
},
|
||||
"asset-list": {
|
||||
"title": "Fichiers",
|
||||
"size-warning": "Ce fichier est volumineux, vous pouvez réduire sa taille avec le Code-splitting"
|
||||
}
|
||||
},
|
||||
"analyzer": {
|
||||
"go-up": "Aller au parent",
|
||||
"go-home": "Aller à la racine",
|
||||
"chunk": "Chunk"
|
||||
},
|
||||
"sizes": {
|
||||
"stats": "Stats",
|
||||
"parsed": "Parsé",
|
||||
"gzip": "Gzip",
|
||||
"help": "<b>Stats:</b> taille depuis les données statistiques de webpack.<br><b>Parsé:</b> taille depuis les sources extraites (après les plugins de minifications). Plus précis.<br><b>Gzip:</b> taille en source extraites compressée."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,10 @@ ClientAddonApi.component('vue-webpack-analyzer', WebpackAnalyzer)
|
||||
ClientAddonApi.addRoutes('vue-webpack', [
|
||||
{ path: '', name: 'test-webpack-route', component: TestView }
|
||||
])
|
||||
|
||||
// Locales
|
||||
const locales = require.context('./locales', true, /[a-z0-9]+\.json$/i)
|
||||
locales.keys().forEach(key => {
|
||||
const locale = key.match(/([a-z0-9]+)\./i)[1]
|
||||
ClientAddonApi.addLocalization(locale, locales(key))
|
||||
})
|
||||
|
||||
@@ -30,9 +30,5 @@ export default {
|
||||
value
|
||||
})
|
||||
})
|
||||
|
||||
this.sizeHelp = `<b>Stats:</b> size from webpack stats data.<br>
|
||||
<b>Parsed:</b> size from extracted source (after minification plugins). More accurate.<br>
|
||||
<b>Gzip:</b> size of gzipped extracted source.`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"apollo-link-ws": "^1.0.0",
|
||||
"apollo-utilities": "^1.0.9",
|
||||
"clone": "^1.0.4",
|
||||
"deepmerge": "^2.0.1",
|
||||
"express-history-api-fallback": "^2.2.1",
|
||||
"file-icons-js": "^1.0.3",
|
||||
"graphql": "^0.13.0",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<StatusBar/>
|
||||
<ClientAddonLoader/>
|
||||
<LocaleLoader/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -22,15 +22,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$_scripts = new Map()
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadAddon (addon) {
|
||||
console.log(`Loading addon ${addon.id} (${addon.url})...`)
|
||||
console.log(`[UI] Loading client addon ${addon.id} (${addon.url})...`)
|
||||
const script = document.createElement('script')
|
||||
this.$_scripts.set(addon.id, script)
|
||||
script.setAttribute('src', addon.url)
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<ListItemInfo
|
||||
:name="configuration.name"
|
||||
:description="configuration.description"
|
||||
:description="$t(configuration.description)"
|
||||
:selected="selected"
|
||||
/>
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
box-center()
|
||||
|
||||
.list-item-info
|
||||
flex 100% 1 1
|
||||
flex auto 1 1
|
||||
width 0
|
||||
|
||||
>>> .description
|
||||
|
||||
38
packages/@vue/cli-ui/src/components/LocaleLoader.vue
Normal file
38
packages/@vue/cli-ui/src/components/LocaleLoader.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { mergeLocale } from '../i18n'
|
||||
|
||||
import LOCALES from '../graphql/locales.gql'
|
||||
import LOCALE_ADDED from '../graphql/localeAdded.gql'
|
||||
|
||||
export default {
|
||||
apollo: {
|
||||
locales: {
|
||||
query: LOCALES,
|
||||
manual: true,
|
||||
result ({ data: { locales } }) {
|
||||
locales.forEach(this.loadLocale)
|
||||
}
|
||||
},
|
||||
|
||||
$subscribe: {
|
||||
localeAdded: {
|
||||
query: LOCALE_ADDED,
|
||||
result ({ data }) {
|
||||
this.loadLocale(data.localeAdded)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadLocale (locale) {
|
||||
console.log(`[UI] Locale ${locale.lang} updated with new strings`)
|
||||
mergeLocale(locale.lang, locale.strings)
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
@import "~@/style/imports"
|
||||
|
||||
.nav-list
|
||||
overflow-x none
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
background $color-background-light
|
||||
</style>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
>
|
||||
<div class="prompt-content">
|
||||
<ListItemInfo
|
||||
:name="prompt.message"
|
||||
:description="prompt.description"
|
||||
:name="$t(prompt.message)"
|
||||
:description="$t(prompt.description)"
|
||||
:link="prompt.link"
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
class="right"
|
||||
@input="value => asnwerCheckbox(choice, value)"
|
||||
>
|
||||
{{ choice.name }}
|
||||
{{ $t(choice.name) }}
|
||||
</VueSwitch>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
@input="value => answer(value)"
|
||||
>
|
||||
<ListItemInfo
|
||||
:name="prompt.message"
|
||||
:description="prompt.description"
|
||||
:name="$t(prompt.message)"
|
||||
:description="$t(prompt.description)"
|
||||
:link="prompt.link"
|
||||
/>
|
||||
</VueSwitch>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="error" class="prompt-error">
|
||||
<div class="vue-ui-text danger banner">
|
||||
<VueIcon icon="warning" class="big"/>
|
||||
<span>{{ error.message }}</span>
|
||||
<span>{{ $t(error.message) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
>
|
||||
<div class="prompt-content">
|
||||
<ListItemInfo
|
||||
:name="prompt.message"
|
||||
:description="prompt.description"
|
||||
:name="$t(prompt.message)"
|
||||
:description="$t(prompt.description)"
|
||||
:link="prompt.link"
|
||||
/>
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
>
|
||||
<div class="prompt-content">
|
||||
<ListItemInfo
|
||||
:name="prompt.message"
|
||||
:description="prompt.description"
|
||||
:name="$t(prompt.message)"
|
||||
:description="$t(prompt.description)"
|
||||
:link="prompt.link"
|
||||
/>
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
generateLabel (choice) {
|
||||
let label = choice.name
|
||||
let label = this.$t(choice.name)
|
||||
if (choice.isDefault) {
|
||||
label += ` (${this.$t('components.prompt-list.default')})`
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@ module.exports = {
|
||||
CLIENT_ADDON_ADDED: 'client_addon_added',
|
||||
SHARED_DATA_UPDATED: 'shared_data_updated',
|
||||
PLUGIN_ACTION_CALLED: 'plugin_action_called',
|
||||
PLUGIN_ACTION_RESOLVED: 'plugin_action_resolved'
|
||||
PLUGIN_ACTION_RESOLVED: 'plugin_action_resolved',
|
||||
LOCALE_ADDED: 'locale_added'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const path = require('path')
|
||||
// Subs
|
||||
const channels = require('../channels')
|
||||
// Utils
|
||||
const { getBasePath } = require('../utils/serve')
|
||||
const { resolveModuleRoot } = require('../utils/resolve-path')
|
||||
|
||||
let addons = []
|
||||
|
||||
@@ -43,7 +43,7 @@ function serve (req, res) {
|
||||
const { id, 0: file } = req.params
|
||||
const addon = findOne(id)
|
||||
if (addon && addon.path) {
|
||||
const basePath = getBasePath(require.resolve(addon.path))
|
||||
const basePath = resolveModuleRoot(require.resolve(addon.path))
|
||||
if (basePath) {
|
||||
res.sendFile(path.join(basePath, file))
|
||||
return
|
||||
|
||||
26
packages/@vue/cli-ui/src/graphql-api/connectors/locales.js
Normal file
26
packages/@vue/cli-ui/src/graphql-api/connectors/locales.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Subs
|
||||
const channels = require('../channels')
|
||||
|
||||
let locales = []
|
||||
|
||||
function list (context) {
|
||||
return locales
|
||||
}
|
||||
|
||||
function add ({ lang, strings }, context) {
|
||||
const locale = { lang, strings }
|
||||
locales.push(locale)
|
||||
context.pubsub.publish(channels.LOCALE_ADDED, {
|
||||
localeAdded: locale
|
||||
})
|
||||
}
|
||||
|
||||
function clear (context) {
|
||||
locales = []
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list,
|
||||
add,
|
||||
clear
|
||||
}
|
||||
@@ -17,6 +17,7 @@ const {
|
||||
} = require('@vue/cli/lib/util/installDeps')
|
||||
const invoke = require('@vue/cli/lib/invoke')
|
||||
const notifier = require('node-notifier')
|
||||
const globby = require('globby')
|
||||
// Subs
|
||||
const channels = require('../channels')
|
||||
// Connectors
|
||||
@@ -27,11 +28,12 @@ const progress = require('./progress')
|
||||
const logs = require('./logs')
|
||||
const clientAddons = require('./client-addons')
|
||||
const views = require('./views')
|
||||
const locales = require('./locales')
|
||||
// Api
|
||||
const PluginApi = require('../api/PluginApi')
|
||||
// Utils
|
||||
const { getCommand } = require('../utils/command')
|
||||
const { getBasePath } = require('../utils/serve')
|
||||
const { resolveModuleRoot } = require('../utils/resolve-path')
|
||||
const ipc = require('../utils/ipc')
|
||||
|
||||
const PROGRESS_ID = 'plugin-installation'
|
||||
@@ -54,7 +56,7 @@ let installationStep
|
||||
let projectId
|
||||
|
||||
function getPath (id) {
|
||||
return path.dirname(resolveModule(id, cwd.get()))
|
||||
return resolveModuleRoot(resolveModule(id, cwd.get()), id)
|
||||
}
|
||||
|
||||
function findPlugins (deps) {
|
||||
@@ -91,7 +93,7 @@ function resetPluginApi (context) {
|
||||
// Run Plugin API
|
||||
runPluginApi('@vue/cli-service', context)
|
||||
plugins.forEach(plugin => runPluginApi(plugin.id, context))
|
||||
runPluginApi('.', context, 'vue-cli-ui')
|
||||
runPluginApi(cwd.get(), context, 'vue-cli-ui')
|
||||
// Add client addons
|
||||
pluginApi.clientAddons.forEach(options => clientAddons.add(options, context))
|
||||
// Add views
|
||||
@@ -121,6 +123,18 @@ function runPluginApi (id, context, fileName = 'ui') {
|
||||
module(pluginApi)
|
||||
pluginApi.pluginId = null
|
||||
}
|
||||
|
||||
// Locales
|
||||
try {
|
||||
const folder = fs.existsSync(id) ? id : getPath(id)
|
||||
const paths = globby.sync([path.join(folder, './locales/*.json')])
|
||||
paths.forEach(file => {
|
||||
const basename = path.basename(file)
|
||||
const lang = basename.substr(0, basename.indexOf('.'))
|
||||
const strings = JSON.parse(fs.readFileSync(file, { encoding: 'utf8' }))
|
||||
locales.add({ lang, strings }, context)
|
||||
})
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function findOne (id, context) {
|
||||
@@ -367,7 +381,7 @@ async function callAction ({ id, params }, context) {
|
||||
|
||||
function serve (req, res) {
|
||||
const { id, 0: file } = req.params
|
||||
const basePath = getBasePath(require.resolve(id), id)
|
||||
const basePath = id === '.' ? cwd.get() : getPath(id)
|
||||
if (basePath) {
|
||||
res.sendFile(path.join(basePath, 'ui-public', file))
|
||||
return
|
||||
|
||||
@@ -14,6 +14,7 @@ const cwd = require('./cwd')
|
||||
const prompts = require('./prompts')
|
||||
const folders = require('./folders')
|
||||
const plugins = require('./plugins')
|
||||
const locales = require('./locales')
|
||||
// Context
|
||||
const getContext = require('../context')
|
||||
|
||||
@@ -316,6 +317,8 @@ async function open (id, context) {
|
||||
lastProject = currentProject
|
||||
currentProject = project
|
||||
cwd.set(project.path, context)
|
||||
// Reset locales
|
||||
locales.clear()
|
||||
// Load plugins
|
||||
plugins.list(project.path, context)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const progress = require('./connectors/progress')
|
||||
const files = require('./connectors/files')
|
||||
const clientAddons = require('./connectors/client-addons')
|
||||
const sharedData = require('./connectors/shared-data')
|
||||
const locales = require('./connectors/locales')
|
||||
// Start ipc server
|
||||
require('./utils/ipc')
|
||||
|
||||
@@ -37,7 +38,8 @@ const resolvers = [{
|
||||
cwd: () => cwd.get(),
|
||||
progress: (root, { id }, context) => progress.get(id, context),
|
||||
clientAddons: (root, args, context) => clientAddons.list(context),
|
||||
sharedData: (root, { id }, context) => sharedData.get(id, context)
|
||||
sharedData: (root, { id }, context) => sharedData.get(id, context),
|
||||
locales: (root, args, context) => locales.list(context)
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@@ -73,6 +75,9 @@ const resolvers = [{
|
||||
(parent, args, { pubsub }) => pubsub.asyncIterator(channels.SHARED_DATA_UPDATED),
|
||||
(payload, vars) => payload.sharedDataUpdated.id === vars.id
|
||||
)
|
||||
},
|
||||
localeAdded: {
|
||||
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.LOCALE_ADDED)
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -54,11 +54,17 @@ type SharedData {
|
||||
value: JSON
|
||||
}
|
||||
|
||||
type Locale {
|
||||
lang: String!
|
||||
strings: JSON!
|
||||
}
|
||||
|
||||
type Query {
|
||||
progress (id: ID!): Progress
|
||||
cwd: String!
|
||||
clientAddons: [ClientAddon]
|
||||
sharedData (id: ID!): SharedData
|
||||
locales: [Locale]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -72,6 +78,7 @@ type Subscription {
|
||||
cwdChanged: String!
|
||||
clientAddonAdded: ClientAddon
|
||||
sharedDataUpdated (id: ID!): SharedData
|
||||
localeAdded: Locale
|
||||
}
|
||||
`]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.getBasePath = function (filePath, id = null) {
|
||||
exports.resolveModuleRoot = function (filePath, id = null) {
|
||||
{
|
||||
const index = filePath.lastIndexOf('/index.js')
|
||||
if (index !== -1) {
|
||||
7
packages/@vue/cli-ui/src/graphql/localeAdded.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/localeAdded.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./localeFragment.gql"
|
||||
|
||||
subscription localeAdded {
|
||||
localeAdded {
|
||||
...locale
|
||||
}
|
||||
}
|
||||
4
packages/@vue/cli-ui/src/graphql/localeFragment.gql
Normal file
4
packages/@vue/cli-ui/src/graphql/localeFragment.gql
Normal file
@@ -0,0 +1,4 @@
|
||||
fragment locale on Locale {
|
||||
lang
|
||||
strings
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/locales.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/locales.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./localeFragment.gql"
|
||||
|
||||
query locales {
|
||||
locales {
|
||||
...locale
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import deepmerge from 'deepmerge'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
@@ -30,4 +31,9 @@ const i18n = new VueI18n({
|
||||
messages: loadLocaleMessages()
|
||||
})
|
||||
|
||||
export function mergeLocale (lang, messages) {
|
||||
const newData = deepmerge(i18n.getLocaleMessage(lang), messages)
|
||||
i18n.setLocaleMessage(lang, newData)
|
||||
}
|
||||
|
||||
export default i18n
|
||||
|
||||
@@ -62,7 +62,8 @@
|
||||
"tooltips": {
|
||||
"plugins": "Plugins",
|
||||
"configuration": "Configuration",
|
||||
"tasks": "Tâches"
|
||||
"tasks": "Tâches",
|
||||
"more": "Plus"
|
||||
}
|
||||
},
|
||||
"project-select-list": {
|
||||
@@ -275,6 +276,12 @@
|
||||
"project-configurations": {
|
||||
"title": "Configuration du projet"
|
||||
},
|
||||
"project-configuration-details": {
|
||||
"actions": {
|
||||
"cancel": "Annuler les changements",
|
||||
"save": "Sauvegarder les modifications"
|
||||
}
|
||||
},
|
||||
"project-tasks": {
|
||||
"title": "Tâches du projet"
|
||||
},
|
||||
@@ -288,6 +295,13 @@
|
||||
"parameters": "Paramètres",
|
||||
"more-info": "Plus d'infos",
|
||||
"output": "Sortie"
|
||||
},
|
||||
"about": {
|
||||
"title": "A propos",
|
||||
"description": "<a href=\"https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-ui\" target=\"_blank\">@vue/cli-ui</a> est un paquet inclus dans @vue/cli qui affiche une interface graphique.",
|
||||
"quote": "Vue-cli 3.x is a complete rewrite, with a lot of new awesome features. You will be to select features like routing, Vuex or Typescript, then add and upgrade building blocks called \"vue-cli plugins\". But having so much more options also means the tool is now more complex and harder to start using. That's why we thought having a full-blown GUI would help discover the new features, search and install vue-cli plugins and unlock more possibilities overall while not being limited by a terminal interface. To sum up, vue-cli will not only allow you to bootstrap a new project easily, but it will also remain useful for ongoing work afterwards!",
|
||||
"links": "Liens utiles",
|
||||
"back": "Retour"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import router from '../router'
|
||||
import { mergeLocale } from '../i18n'
|
||||
import ProjectHome from '../views/ProjectHome.vue'
|
||||
|
||||
export default class ClientAddonApi {
|
||||
@@ -18,7 +19,7 @@ export default class ClientAddonApi {
|
||||
this.components.set(id, definition)
|
||||
const componentId = toComponentId(id)
|
||||
Vue.component(componentId, definition)
|
||||
console.log(`Registered ${componentId} component`)
|
||||
console.log(`[ClientAddonApi] Registered ${componentId} component`)
|
||||
// Call listeners
|
||||
const listeners = this.componentListeners.get(id)
|
||||
if (listeners) {
|
||||
@@ -47,6 +48,18 @@ export default class ClientAddonApi {
|
||||
children: routes
|
||||
}
|
||||
])
|
||||
console.log(`[ClientAddonApi] Registered new routes under the /addon/${id} route`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new strings into the specified lang translations (using vue-i18n).
|
||||
*
|
||||
* @param {string} lang Locale to merge to (ex: 'en', 'fr'...)
|
||||
* @param {object} strings A vue-i18n strings object containing the translations
|
||||
*/
|
||||
addLocalization (lang, strings) {
|
||||
mergeLocale(lang, strings)
|
||||
console.log(`[ClientAddonApi] Registered new strings for locale ${lang}`)
|
||||
}
|
||||
|
||||
/* Internal */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="header">
|
||||
<VueIcon icon="assignment" class="task-icon big"/>
|
||||
<div class="name">{{ task.name }}</div>
|
||||
<div class="description">{{ task.description }}</div>
|
||||
<div class="description">{{ $t(task.description) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
@@ -66,7 +66,7 @@
|
||||
:key="view.id"
|
||||
:value="view.id"
|
||||
:icon-left="view.icon"
|
||||
:label="view.label"
|
||||
:label="$t(view.label)"
|
||||
/>
|
||||
</VueGroup>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user