mirror of
https://github.com/mirego/accent.git
synced 2024-08-16 14:30:38 +03:00
Add cli in the repo to truly become a MONOREPO 🎉
This commit is contained in:
parent
b2750d0b60
commit
be9f6bed83
1
cli/.eslintignore
Normal file
1
cli/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
/lib
|
3
cli/.eslintrc
Normal file
3
cli/.eslintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "oclif"
|
||||
}
|
6
cli/.gitignore
vendored
Normal file
6
cli/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*-debug.log
|
||||
*-error.log
|
||||
.oclif.manifest.json
|
||||
/lib
|
||||
/node_modules
|
||||
/tmp
|
9
cli/LICENSE.md
Normal file
9
cli/LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
||||
Copyright (c) 2018, Mirego All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of the Mirego nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
173
cli/README.md
Normal file
173
cli/README.md
Normal file
@ -0,0 +1,173 @@
|
||||
Accent CLI
|
||||
======
|
||||
|
||||
[![Version](https://img.shields.io/npm/v/accent-cli.svg)](https://npmjs.org/package/accent-cli)
|
||||
[![Build Status](https://img.shields.io/travis/v/accent-cli.svg?branch=master)](https://travis-ci.com/mirego/accent-cli)
|
||||
|
||||
<!-- toc -->
|
||||
* [Usage](#usage)
|
||||
* [Configuration](#configuration)
|
||||
* [Commands](#commands)
|
||||
* [License](#license)
|
||||
* [About Mirego](#about-mirego)
|
||||
<!-- tocstop -->
|
||||
|
||||
# Usage
|
||||
<!-- usage -->
|
||||
```sh-session
|
||||
$ npm install -g accent-cli
|
||||
$ accent COMMAND
|
||||
running command...
|
||||
$ accent (-v|--version|version)
|
||||
accent-cli/0.6.0 darwin-x64 node-v9.5.0
|
||||
$ accent --help [COMMAND]
|
||||
USAGE
|
||||
$ accent COMMAND
|
||||
...
|
||||
```
|
||||
<!-- usagestop -->
|
||||
|
||||
# Configuration
|
||||
|
||||
accent-cli reads from a `accent.json` file. The file should contain valid JSON representing the configuration of your project.
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
{
|
||||
"apiUrl": "http://your.accent.instance",
|
||||
"apiKey": "2nziVSaa8yUJxLkwoZA",
|
||||
"files": [
|
||||
{
|
||||
"language": "fr",
|
||||
"format": "json",
|
||||
"source": "localization/fr/*.json",
|
||||
"target": "localization/%slug%/%original_file_name%.json",
|
||||
"hooks": {
|
||||
"afterSync": "touch sync-done.txt"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Document configuration
|
||||
|
||||
Each operation section `sync` and `addTranslations` can contain the following object:
|
||||
|
||||
- `language`: The identifier of the document’s language
|
||||
- `format`: The format of the document
|
||||
- `source`: The path of the document. This can contain glob pattern (See [the node glob library] used as a dependancy (https://github.com/isaacs/node-glob))
|
||||
- `target`: Path of the target languages
|
||||
- `hooks`: List of hooks to be run
|
||||
|
||||
## Hooks
|
||||
|
||||
Here is a list of available hooks. Those are self-explanatory
|
||||
|
||||
- `beforeSync`
|
||||
- `afterSync`
|
||||
- `beforeExport`
|
||||
- `afterExport`
|
||||
|
||||
# Commands
|
||||
<!-- commands -->
|
||||
* [`accent export`](#accent-export)
|
||||
* [`accent help [COMMAND]`](#accent-help-command)
|
||||
* [`accent jipt PSEUDOLANGUAGENAME`](#accent-jipt-pseudolanguagename)
|
||||
* [`accent stats`](#accent-stats)
|
||||
* [`accent sync`](#accent-sync)
|
||||
|
||||
## `accent export`
|
||||
|
||||
Export files from Accent and write them to your local filesystem
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ accent export
|
||||
|
||||
OPTIONS
|
||||
--order-by=index|key-asc [default: index] Will be used in the export call as the order of the keys
|
||||
|
||||
EXAMPLE
|
||||
$ accent export
|
||||
```
|
||||
|
||||
## `accent help [COMMAND]`
|
||||
|
||||
display help for accent
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ accent help [COMMAND]
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND command to show help for
|
||||
|
||||
OPTIONS
|
||||
--all see all commands in CLI
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.1.4/src/commands/help.ts)_
|
||||
|
||||
## `accent jipt PSEUDOLANGUAGENAME`
|
||||
|
||||
Export jipt files from Accent and write them to your local filesystem
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ accent jipt PSEUDOLANGUAGENAME
|
||||
|
||||
ARGUMENTS
|
||||
PSEUDOLANGUAGENAME The pseudo language for in-place-translation-editing
|
||||
|
||||
EXAMPLE
|
||||
$ accent jipt
|
||||
```
|
||||
|
||||
## `accent stats`
|
||||
|
||||
Fetch stats from the API and display it beautifully
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ accent stats
|
||||
|
||||
EXAMPLE
|
||||
$ accent stats
|
||||
```
|
||||
|
||||
## `accent sync`
|
||||
|
||||
Sync files in Accent and write them to your local filesystem
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ accent sync
|
||||
|
||||
OPTIONS
|
||||
--add-translations Add translations in Accent to help translators if you already have translated
|
||||
strings
|
||||
|
||||
--merge-type=smart|passive|force [default: smart] Will be used in the add translations call as the "merge_type" param
|
||||
|
||||
--order-by=index|key-asc [default: index] Will be used in the export call as the order of the keys
|
||||
|
||||
--sync-type=smart|passive [default: smart] Will be used in the sync call as the "sync_type" param
|
||||
|
||||
--write Write the file from the export _after_ the operation
|
||||
|
||||
EXAMPLE
|
||||
$ accent sync
|
||||
```
|
||||
<!-- commandsstop -->
|
||||
|
||||
# License
|
||||
|
||||
`accent-cli` is © 2019 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/accent-cli/blob/master/LICENSE.md) file.
|
||||
|
||||
# About Mirego
|
||||
|
||||
[Mirego](http://mirego.com) is a team of passionate people who believe that work is a place where you can innovate and have fun. We’re a team of [talented people](http://life.mirego.com) who imagine and build beautiful Web and mobile applications. We come together to share ideas and [change the world](http://mirego.org).
|
||||
|
||||
We also [love open-source software](http://open.mirego.com) and we try to give back to the community as much as we can.
|
4
cli/bin/run
Executable file
4
cli/bin/run
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('@oclif/command').run()
|
||||
.catch(require('@oclif/errors/handle'))
|
3
cli/bin/run.cmd
Normal file
3
cli/bin/run.cmd
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
24
cli/examples/core/accent.json
Normal file
24
cli/examples/core/accent.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "fr",
|
||||
"format": "json",
|
||||
"source": "core/*.json",
|
||||
"target": "core/%original_file_name%-%slug%.json",
|
||||
"hooks": {
|
||||
"beforeSync": [
|
||||
"rm -rf core",
|
||||
"mkdir -p core",
|
||||
"ruby core.rb unmerge translations.json fr > core/core-is-awesome.json"
|
||||
],
|
||||
"beforeAddTranslations": [
|
||||
"ruby core.rb unmerge translations.json en > core/core-is-awesome-en.json"
|
||||
],
|
||||
"afterExport": [
|
||||
"ruby core.rb merge core/core-is-awesome-en.json core/core-is-awesome-fr.json > translations.json",
|
||||
"rm -rf core"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
49
cli/examples/core/core.rb
Normal file
49
cli/examples/core/core.rb
Normal file
@ -0,0 +1,49 @@
|
||||
# Merge and unmerge files to be "core translations" compliant and Accent compliant.
|
||||
|
||||
# ## Merge
|
||||
# The file used in this command contains en and fr translations and outputs
|
||||
# "key" => value in the specified language.
|
||||
#
|
||||
# Given the file en.json:
|
||||
# {"key": "foo"}
|
||||
# Given the file fr.json:
|
||||
# {"key": "bar"}
|
||||
# ruby core.rb merge en.json fr.json
|
||||
# {
|
||||
# "key": {
|
||||
# "en": "bar",
|
||||
# "fr": "foo"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# ## Unmerge
|
||||
# The file used in this command contains en and fr translations and outputs
|
||||
# "key" => value in the specified language.
|
||||
#
|
||||
# Given the file:
|
||||
# {
|
||||
# "key": {
|
||||
# "en": "bar",
|
||||
# "fr": "foo"
|
||||
# }
|
||||
# }
|
||||
# `ruby core.rb unmerge file.json fr`
|
||||
#
|
||||
# {
|
||||
# "key": "foo"
|
||||
# }
|
||||
|
||||
require 'json'
|
||||
|
||||
if ARGV[0] === 'merge'
|
||||
en_json = JSON.parse(File.read(ARGV[1]))
|
||||
fr_json = JSON.parse(File.read(ARGV[2]))
|
||||
|
||||
output = en_json.each_with_object({}) { |(key, value), memo| memo[key] = {fr: fr_json[key], en: value} }
|
||||
puts JSON.pretty_generate(output)
|
||||
end
|
||||
|
||||
if ARGV[0] === 'unmerge'
|
||||
output = JSON.parse(File.read(ARGV[1])).each_with_object({}) { |(key, value), memo| memo[key] = value[ARGV[2]] }
|
||||
puts JSON.pretty_generate(output)
|
||||
end
|
6
cli/examples/core/translations.json
Normal file
6
cli/examples/core/translations.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"key": {
|
||||
"fr": "valeur",
|
||||
"en": "value"
|
||||
}
|
||||
}
|
10
cli/examples/ember/accent.json
Normal file
10
cli/examples/ember/accent.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "en",
|
||||
"format": "json",
|
||||
"source": "translations/en.json",
|
||||
"target": "translations/%slug%.json"
|
||||
}
|
||||
]
|
||||
}
|
5
cli/examples/ember/translations/en.json
Normal file
5
cli/examples/ember/translations/en.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"key": {
|
||||
"nested": "Value"
|
||||
}
|
||||
}
|
5
cli/examples/ember/translations/fr.json
Normal file
5
cli/examples/ember/translations/fr.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"key": {
|
||||
"nested": "Valeur"
|
||||
}
|
||||
}
|
10
cli/examples/phoenix/accent.json
Normal file
10
cli/examples/phoenix/accent.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "en",
|
||||
"format": "gettext",
|
||||
"source": "priv/en/*.po",
|
||||
"target": "priv/%slug%/%original_file_name%.po"
|
||||
}
|
||||
]
|
||||
}
|
6
cli/examples/phoenix/priv/en/my-file.po
Normal file
6
cli/examples/phoenix/priv/en/my-file.po
Normal file
@ -0,0 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: en"
|
||||
|
||||
msgid "key"
|
||||
msgstr "Value"
|
6
cli/examples/phoenix/priv/fr/my-file.po
Normal file
6
cli/examples/phoenix/priv/fr/my-file.po
Normal file
@ -0,0 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: fr"
|
||||
|
||||
msgid "key"
|
||||
msgstr "Valeur"
|
2
cli/examples/rails-with-aigu/.aigu.yml
Normal file
2
cli/examples/rails-with-aigu/.aigu.yml
Normal file
@ -0,0 +1,2 @@
|
||||
output-directory: config/locales
|
||||
input-directory: config/locales
|
3
cli/examples/rails-with-aigu/Gemfile
Normal file
3
cli/examples/rails-with-aigu/Gemfile
Normal file
@ -0,0 +1,3 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'aigu'
|
17
cli/examples/rails-with-aigu/Gemfile.lock
Normal file
17
cli/examples/rails-with-aigu/Gemfile.lock
Normal file
@ -0,0 +1,17 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
aigu (1.2)
|
||||
nokogiri (~> 1.6)
|
||||
mini_portile2 (2.3.0)
|
||||
nokogiri (1.8.5)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
aigu
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.2
|
29
cli/examples/rails-with-aigu/accent.json
Normal file
29
cli/examples/rails-with-aigu/accent.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "en",
|
||||
"format": "json",
|
||||
"source": "aigu/*.json",
|
||||
"target": "aigu/%original_file_name%-%slug%.json",
|
||||
"hooks": {
|
||||
"beforeSync": [
|
||||
"rm -rf aigu",
|
||||
"mkdir -p aigu",
|
||||
"aigu rails-export --locale=en --output-file=aigu/aigu-is-awesome.json >/dev/null 2>&1"
|
||||
],
|
||||
"beforeAddTranslations": [
|
||||
"aigu rails-export --locale=fr --output-file=aigu/aigu-is-awesome-fr.json >/dev/null 2>&1"
|
||||
],
|
||||
"beforeExport": [
|
||||
"rm -rf aigu",
|
||||
"mkdir -p aigu"
|
||||
],
|
||||
"afterExport": [
|
||||
"aigu rails-import --locale=en --input-file=aigu/aigu-is-awesome-en.json >/dev/null 2>&1",
|
||||
"aigu rails-import --locale=fr --input-file=aigu/aigu-is-awesome-fr.json >/dev/null 2>&1",
|
||||
"rm -rf aigu"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
en:
|
||||
my_file:
|
||||
key: Value
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
fr:
|
||||
my_file:
|
||||
key: Valeur
|
10
cli/examples/rails/accent.json
Normal file
10
cli/examples/rails/accent.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "en",
|
||||
"format": "rails_yml",
|
||||
"source": "config/locales/en/*.yml",
|
||||
"target": "config/locales/%slug%/%original_file_name%.yml"
|
||||
}
|
||||
]
|
||||
}
|
4
cli/examples/rails/config/locales/en/my_file.yml
Normal file
4
cli/examples/rails/config/locales/en/my_file.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
en:
|
||||
my_file:
|
||||
key: Value
|
4
cli/examples/rails/config/locales/fr/my_file.yml
Normal file
4
cli/examples/rails/config/locales/fr/my_file.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
fr:
|
||||
my_file:
|
||||
key: Valeur
|
10
cli/examples/react/accent.json
Normal file
10
cli/examples/react/accent.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"language": "fr",
|
||||
"format": "json",
|
||||
"source": "src/locales/fr/*.json",
|
||||
"target": "src/locales/%slug%/%original_file_name%.json"
|
||||
}
|
||||
]
|
||||
}
|
6
cli/examples/react/src/locales/accent/admin.json
Normal file
6
cli/examples/react/src/locales/accent/admin.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"organizations": "{^nav.organizations@admin}",
|
||||
"reservations": "{^nav.reservations@admin}"
|
||||
}
|
||||
}
|
239
cli/examples/react/src/locales/accent/common.json
Normal file
239
cli/examples/react/src/locales/accent/common.json
Normal file
@ -0,0 +1,239 @@
|
||||
{
|
||||
"navigation": {
|
||||
"switchLanguage": {
|
||||
"en": "{acc:navigation.switchLanguage.en@common}",
|
||||
"fr": "{acc:navigation.switchLanguage.fr@common}"
|
||||
},
|
||||
"search": "{acc:navigation.search@common}",
|
||||
"admin": {
|
||||
"organizations": "{acc:navigation.admin.organizations@common}",
|
||||
"reservations": "{acc:navigation.admin.reservations@common}",
|
||||
"title": "{acc:navigation.admin.title@common}"
|
||||
},
|
||||
"owner": {
|
||||
"equipments": "{acc:navigation.owner.equipments@common}",
|
||||
"parkingLots": "{acc:navigation.owner.parkingLots@common}",
|
||||
"reservations": "{acc:navigation.owner.reservations@common}",
|
||||
"title": "{acc:navigation.owner.title@common}"
|
||||
},
|
||||
"renter": {
|
||||
"reservations": "{acc:navigation.renter.reservations@common}",
|
||||
"title": "{acc:navigation.renter.title@common}"
|
||||
},
|
||||
"account": {
|
||||
"administration": "{acc:navigation.account.administration@common}",
|
||||
"organization": "{acc:navigation.account.organization@common}"
|
||||
},
|
||||
"logout": "{acc:navigation.logout@common}",
|
||||
"login": "{acc:navigation.login@common}"
|
||||
},
|
||||
"languages": {
|
||||
"english": {
|
||||
"title": "{acc:languages.english.title@common}",
|
||||
"code": "{acc:languages.english.code@common}"
|
||||
},
|
||||
"french": {
|
||||
"title": "{acc:languages.french.title@common}",
|
||||
"code": "{acc:languages.french.code@common}"
|
||||
}
|
||||
},
|
||||
"mileage": {
|
||||
"KILOMETERS": "{acc:mileage.KILOMETERS@common}",
|
||||
"MILES": "{acc:mileage.MILES@common}"
|
||||
},
|
||||
"notFound": {
|
||||
"goHome": "{acc:notFound.goHome@common}",
|
||||
"message": "{acc:notFound.message@common}",
|
||||
"title": "{acc:notFound.title@common}"
|
||||
},
|
||||
"reservations": {
|
||||
"activities": {
|
||||
"chronology": {
|
||||
"application": "{acc:reservations.activities.chronology.application@common}",
|
||||
"date": "{acc:reservations.activities.chronology.date@common}",
|
||||
"synchronization": "{acc:reservations.activities.chronology.synchronization@common}",
|
||||
"title": "{acc:reservations.activities.chronology.title@common}"
|
||||
},
|
||||
"damages": {
|
||||
"date": "{acc:reservations.activities.damages.date@common}",
|
||||
"noDamages": "{acc:reservations.activities.damages.noDamages@common}",
|
||||
"title": "{acc:reservations.activities.damages.title@common}"
|
||||
},
|
||||
"inspection": {
|
||||
"noConditionReports": "{acc:reservations.activities.inspection.noConditionReports@common}",
|
||||
"title": "{acc:reservations.activities.inspection.title@common}"
|
||||
},
|
||||
"licencePlate": {
|
||||
"title": "{acc:reservations.activities.licencePlate.title@common}"
|
||||
},
|
||||
"location": {
|
||||
"addressTitle": "{acc:reservations.activities.location.addressTitle@common}",
|
||||
"error": "{acc:reservations.activities.location.error@common}",
|
||||
"notFound": "{acc:reservations.activities.location.notFound@common}",
|
||||
"notProvided": "{acc:reservations.activities.location.notProvided@common}",
|
||||
"title": "{acc:reservations.activities.location.title@common}"
|
||||
},
|
||||
"odometer": {
|
||||
"notProdived": "{acc:reservations.activities.odometer.notProdived@common}",
|
||||
"title": "{acc:reservations.activities.odometer.title@common}"
|
||||
},
|
||||
"review": {
|
||||
"reasons": {
|
||||
"alreadyPacked": "{acc:reservations.activities.review.reasons.alreadyPacked@common}",
|
||||
"dirty": "{acc:reservations.activities.review.reasons.dirty@common}",
|
||||
"damaged": "{acc:reservations.activities.review.reasons.damaged@common}",
|
||||
"snow": "{acc:reservations.activities.review.reasons.snow@common}",
|
||||
"copled": "{acc:reservations.activities.review.reasons.copled@common}",
|
||||
"notCompliant": "{acc:reservations.activities.review.reasons.notCompliant@common}",
|
||||
"other": "{acc:reservations.activities.review.reasons.other@common}"
|
||||
},
|
||||
"title": "{acc:reservations.activities.review.title@common}"
|
||||
}
|
||||
},
|
||||
"assignees": {
|
||||
"invitationList": {
|
||||
"empty": "{acc:reservations.assignees.invitationList.empty@common}"
|
||||
},
|
||||
"sendInvite": {
|
||||
"form": {
|
||||
"actions": {
|
||||
"submit": {
|
||||
"caption": "{acc:reservations.assignees.sendInvite.form.actions.submit.caption@common}"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"failed": "{acc:reservations.assignees.sendInvite.form.errors.failed@common}"
|
||||
},
|
||||
"fields": {
|
||||
"driverName": {
|
||||
"errors": {
|
||||
"required": "{acc:reservations.assignees.sendInvite.form.fields.driverName.errors.required@common}"
|
||||
},
|
||||
"label": "{acc:reservations.assignees.sendInvite.form.fields.driverName.label@common}",
|
||||
"placeholder": "{acc:reservations.assignees.sendInvite.form.fields.driverName.placeholder@common}"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"errors": {
|
||||
"invalid": "{acc:reservations.assignees.sendInvite.form.fields.phoneNumber.errors.invalid@common}",
|
||||
"required": "{acc:reservations.assignees.sendInvite.form.fields.phoneNumber.errors.required@common}"
|
||||
},
|
||||
"label": "{acc:reservations.assignees.sendInvite.form.fields.phoneNumber.label@common}",
|
||||
"placeholder": "{acc:reservations.assignees.sendInvite.form.fields.phoneNumber.placeholder@common}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stateFilter": {
|
||||
"all": "{acc:reservations.stateFilter.all@common}",
|
||||
"current": "{acc:reservations.stateFilter.current@common}",
|
||||
"cancelled": "{acc:reservations.stateFilter.cancelled@common}",
|
||||
"done": "{acc:reservations.stateFilter.done@common}",
|
||||
"upcoming": "{acc:reservations.stateFilter.upcoming@common}",
|
||||
"late_activation": "{acc:reservations.stateFilter.late_activation@common}",
|
||||
"late_deactivation": "{acc:reservations.stateFilter.late_deactivation@common}"
|
||||
},
|
||||
"statusBadge": {
|
||||
"activated": "{acc:reservations.statusBadge.activated@common}",
|
||||
"deactivated": "{acc:reservations.statusBadge.deactivated@common}",
|
||||
"lateActivation": "{acc:reservations.statusBadge.lateActivation@common}",
|
||||
"lateDeactivation": "{acc:reservations.statusBadge.lateDeactivation@common}",
|
||||
"upcoming": "{acc:reservations.statusBadge.upcoming@common}"
|
||||
}
|
||||
},
|
||||
"search": "{acc:search@common}",
|
||||
"searchMap": {
|
||||
"departure": "{acc:searchMap.departure@common}"
|
||||
},
|
||||
"timeAgo": "{acc:timeAgo@common}",
|
||||
"errors": {
|
||||
"overlap_availabilities": "{acc:errors.overlap_availabilities@common}",
|
||||
"overlap_reservations": "{acc:errors.overlap_reservations@common}",
|
||||
"equipment_not_available": "{acc:errors.equipment_not_available@common}"
|
||||
},
|
||||
"form": {
|
||||
"actions": {
|
||||
"delete": {
|
||||
"caption": "{acc:form.actions.delete.caption@common}"
|
||||
},
|
||||
"cancel": {
|
||||
"caption": "{acc:form.actions.cancel.caption@common}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"prefixSymbol": "{acc:currency.prefixSymbol@common}",
|
||||
"suffixSymbol": "{acc:currency.suffixSymbol@common}"
|
||||
},
|
||||
"trailerTypes": {
|
||||
"dry_van": "{acc:trailerTypes.dry_van@common}",
|
||||
"plate_dry_van": "{acc:trailerTypes.plate_dry_van@common}",
|
||||
"storage_van": "{acc:trailerTypes.storage_van@common}",
|
||||
"reefer_van": "{acc:trailerTypes.reefer_van@common}",
|
||||
"heated_van": "{acc:trailerTypes.heated_van@common}",
|
||||
"flatbed": "{acc:trailerTypes.flatbed@common}",
|
||||
"dropdeck": "{acc:trailerTypes.dropdeck@common}"
|
||||
},
|
||||
"country": {
|
||||
"canada": {
|
||||
"title": "{acc:country.canada.title@common}",
|
||||
"code": "{acc:country.canada.code@common}",
|
||||
"province": {
|
||||
"ontario": {
|
||||
"title": "{acc:country.canada.province.ontario.title@common}",
|
||||
"code": "{acc:country.canada.province.ontario.code@common}"
|
||||
},
|
||||
"quebec": {
|
||||
"title": "{acc:country.canada.province.quebec.title@common}",
|
||||
"code": "{acc:country.canada.province.quebec.code@common}"
|
||||
},
|
||||
"britishColumbia": {
|
||||
"title": "{acc:country.canada.province.britishColumbia.title@common}",
|
||||
"code": "{acc:country.canada.province.britishColumbia.code@common}"
|
||||
},
|
||||
"alberta": {
|
||||
"title": "{acc:country.canada.province.alberta.title@common}",
|
||||
"code": "{acc:country.canada.province.alberta.code@common}"
|
||||
},
|
||||
"manitoba": {
|
||||
"title": "{acc:country.canada.province.manitoba.title@common}",
|
||||
"code": "{acc:country.canada.province.manitoba.code@common}"
|
||||
},
|
||||
"saskatchewan": {
|
||||
"title": "{acc:country.canada.province.saskatchewan.title@common}",
|
||||
"code": "{acc:country.canada.province.saskatchewan.code@common}"
|
||||
},
|
||||
"novaScotia": {
|
||||
"title": "{acc:country.canada.province.novaScotia.title@common}",
|
||||
"code": "{acc:country.canada.province.novaScotia.code@common}"
|
||||
},
|
||||
"newBrunswick": {
|
||||
"title": "{acc:country.canada.province.newBrunswick.title@common}",
|
||||
"code": "{acc:country.canada.province.newBrunswick.code@common}"
|
||||
},
|
||||
"newfoundlandAndLabrador": {
|
||||
"title": "{acc:country.canada.province.newfoundlandAndLabrador.title@common}",
|
||||
"code": "{acc:country.canada.province.newfoundlandAndLabrador.code@common}"
|
||||
},
|
||||
"princeEdwardIsland": {
|
||||
"title": "{acc:country.canada.province.princeEdwardIsland.title@common}",
|
||||
"code": "{acc:country.canada.province.princeEdwardIsland.code@common}"
|
||||
},
|
||||
"northwestTerritories": {
|
||||
"title": "{acc:country.canada.province.northwestTerritories.title@common}",
|
||||
"code": "{acc:country.canada.province.northwestTerritories.code@common}"
|
||||
},
|
||||
"nunavut": {
|
||||
"title": "{acc:country.canada.province.nunavut.title@common}",
|
||||
"code": "{acc:country.canada.province.nunavut.code@common}"
|
||||
},
|
||||
"yukon": {
|
||||
"title": "{acc:country.canada.province.yukon.title@common}",
|
||||
"code": "{acc:country.canada.province.yukon.code@common}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"appTitle": "{acc:appTitle@common}",
|
||||
"fatalError": "{acc:fatalError@common}"
|
||||
}
|
70
cli/examples/react/src/locales/en/admin.json
Normal file
70
cli/examples/react/src/locales/en/admin.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"edit": {
|
||||
"actions": {
|
||||
"actAsOrganization": "Manage"
|
||||
},
|
||||
"errors": {
|
||||
"organizationNotFound": "The organization doesn’t exist"
|
||||
},
|
||||
"title": "Edit Organization"
|
||||
},
|
||||
"form": {
|
||||
"actions": {
|
||||
"submit": {
|
||||
"caption": "Submit"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"email": {
|
||||
"errors": {
|
||||
"invalid": "Must be a valid email address"
|
||||
},
|
||||
"label": "Email Address"
|
||||
},
|
||||
"isOwner": {
|
||||
"label": "Owner"
|
||||
},
|
||||
"isRenter": {
|
||||
"label": "Renter"
|
||||
},
|
||||
"name": {
|
||||
"errors": {
|
||||
"required": "You must enter a name"
|
||||
},
|
||||
"label": "Name"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"errors": {
|
||||
"invalid": "Must be in the form of: +15553219876"
|
||||
},
|
||||
"label": "Phone Number"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"informations": {
|
||||
"title": "Informations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"actions": {
|
||||
"new": "New Organization"
|
||||
},
|
||||
"alerts": {
|
||||
"editOrganizationSuccess": "The organization was updated with success",
|
||||
"newOrganizationSuccess": "The organization was created with success",
|
||||
"noOrganization": "There are currently no organizations"
|
||||
},
|
||||
"organizationList": {
|
||||
"header": {
|
||||
"email": "Email Address",
|
||||
"name": "Name",
|
||||
"phoneNumber": "Phone Number"
|
||||
}
|
||||
},
|
||||
"title": "Organizations"
|
||||
},
|
||||
"new": {
|
||||
"title": "New Organization"
|
||||
}
|
||||
}
|
239
cli/examples/react/src/locales/en/common.json
Normal file
239
cli/examples/react/src/locales/en/common.json
Normal file
@ -0,0 +1,239 @@
|
||||
{
|
||||
"appTitle": "vHub",
|
||||
"fatalError": "An unexpected error has happened. Please try again later.",
|
||||
"navigation": {
|
||||
"admin": {
|
||||
"organizations": "Organizations",
|
||||
"reservations": "Reservations",
|
||||
"title": "Admin"
|
||||
},
|
||||
"owner": {
|
||||
"equipments": "Equipments",
|
||||
"parkingLots": "Parking Lots",
|
||||
"reservations": "Reservations",
|
||||
"title": "Owner"
|
||||
},
|
||||
"renter": {
|
||||
"reservations": "Reservations",
|
||||
"title": "Renter"
|
||||
},
|
||||
"account": {
|
||||
"administration": "Administration",
|
||||
"organization": "Manage Organization"
|
||||
},
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"switchLanguage": {
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
},
|
||||
"search": "Search"
|
||||
},
|
||||
"languages": {
|
||||
"english": {
|
||||
"title": "English",
|
||||
"code": "en"
|
||||
},
|
||||
"french": {
|
||||
"title": "French",
|
||||
"code": "fr"
|
||||
}
|
||||
},
|
||||
"mileage": {
|
||||
"KILOMETERS": "km",
|
||||
"MILES": "mi"
|
||||
},
|
||||
"notFound": {
|
||||
"goHome": "Go back to homepage",
|
||||
"message": "The page you are trying to reach is not available or does not exist.",
|
||||
"title": "Page Not Found"
|
||||
},
|
||||
"reservations": {
|
||||
"activities": {
|
||||
"chronology": {
|
||||
"application": "Device time",
|
||||
"date": "{{date, DD-MM-YYYY, H:mm}}",
|
||||
"synchronization": "Synchronized at",
|
||||
"title": "Chronology"
|
||||
},
|
||||
"damages": {
|
||||
"date": "{{date, D MMMM YYYY, H:mm}}",
|
||||
"noDamages": "No damage declared",
|
||||
"title": "Damages"
|
||||
},
|
||||
"inspection": {
|
||||
"noConditionReports": "No condition reports declared",
|
||||
"title": "Inspection"
|
||||
},
|
||||
"licencePlate": {
|
||||
"title": "License Plate"
|
||||
},
|
||||
"location": {
|
||||
"addressTitle": "Driver’s position",
|
||||
"error": "Cannot resolve the address. Try again later.",
|
||||
"notFound": "No addresses were found at this location",
|
||||
"notProvided": "Not provided",
|
||||
"title": "Location"
|
||||
},
|
||||
"odometer": {
|
||||
"notProdived": "No information was provided.",
|
||||
"title": "Odometer"
|
||||
},
|
||||
"review": {
|
||||
"reasons": {
|
||||
"alreadyPacked": "Not empty",
|
||||
"dirty": "Dirty interior",
|
||||
"damaged": "Severely damaged",
|
||||
"snow": "Covered with snow",
|
||||
"copled": "Coupled or inaccessible",
|
||||
"notCompliant": "Not road compliant",
|
||||
"other": "Other"
|
||||
},
|
||||
"title": "Review"
|
||||
}
|
||||
},
|
||||
"assignees": {
|
||||
"invitationList": {
|
||||
"empty": "No invitations were sent yet"
|
||||
},
|
||||
"sendInvite": {
|
||||
"form": {
|
||||
"actions": {
|
||||
"submit": {
|
||||
"caption": "Send"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"failed": "We cannot send the SMS at the moment. Try again later."
|
||||
},
|
||||
"fields": {
|
||||
"driverName": {
|
||||
"errors": {
|
||||
"required": "You must enter a name"
|
||||
},
|
||||
"label": "Name",
|
||||
"placeholder": "John Doe"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"errors": {
|
||||
"invalid": "Must be in the form of: +15553219876",
|
||||
"required": "You must enter a phone number"
|
||||
},
|
||||
"label": "Phone Number",
|
||||
"placeholder": "+15553219876"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stateFilter": {
|
||||
"all": "All",
|
||||
"current": "In Progress",
|
||||
"cancelled": "Cancelled",
|
||||
"done": "Done",
|
||||
"upcoming": "Upcoming",
|
||||
"late_activation": "Late activation",
|
||||
"late_deactivation": "Late deactivation"
|
||||
},
|
||||
"statusBadge": {
|
||||
"activated": "Activated at {{date, DD/MM/YYYY HH:mm}}",
|
||||
"deactivated": "Deactivated at {{date, DD/MM/YYYY HH:mm}}",
|
||||
"lateActivation": "Late activation {{date, DD/MM/YYYY}}",
|
||||
"lateDeactivation": "Late deactivation {{date, DD/MM/YYYY}}",
|
||||
"upcoming": "Upcoming"
|
||||
}
|
||||
},
|
||||
"search": "Search",
|
||||
"searchMap": {
|
||||
"departure": "Departure"
|
||||
},
|
||||
"timeAgo": "{{distance}} ago",
|
||||
"errors": {
|
||||
"overlap_availabilities": "Selected dates overlap an existing availability",
|
||||
"overlap_reservations": "Selected dates overlap an existing reservation",
|
||||
"equipment_not_available": "The equipment is not available for those dates"
|
||||
},
|
||||
"form": {
|
||||
"actions": {
|
||||
"delete": {
|
||||
"caption": "Delete"
|
||||
},
|
||||
"cancel": {
|
||||
"caption": "Cancel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"prefixSymbol": "$",
|
||||
"suffixSymbol": ""
|
||||
},
|
||||
"trailerTypes": {
|
||||
"dry_van": "Dry Van",
|
||||
"plate_dry_van": "Plate Dry Van",
|
||||
"storage_van": "Storage Van",
|
||||
"reefer_van": "Reefer Van",
|
||||
"heated_van": "Heated Van",
|
||||
"flatbed": "Flatbed",
|
||||
"dropdeck": "Dropdeck"
|
||||
},
|
||||
"country": {
|
||||
"canada": {
|
||||
"title": "Canada",
|
||||
"code": "CA",
|
||||
"province": {
|
||||
"ontario": {
|
||||
"title": "Ontario",
|
||||
"code": "ON"
|
||||
},
|
||||
"quebec": {
|
||||
"title": "Quebec",
|
||||
"code": "QC"
|
||||
},
|
||||
"britishColumbia": {
|
||||
"title": "British Columbia",
|
||||
"code": "BC"
|
||||
},
|
||||
"alberta": {
|
||||
"title": "Alberta",
|
||||
"code": "AB"
|
||||
},
|
||||
"manitoba": {
|
||||
"title": "Manitoba",
|
||||
"code": "MB"
|
||||
},
|
||||
"saskatchewan": {
|
||||
"title": "Saskatchewan",
|
||||
"code": "SK"
|
||||
},
|
||||
"novaScotia": {
|
||||
"title": "Nova Scotia",
|
||||
"code": "NS"
|
||||
},
|
||||
"newBrunswick": {
|
||||
"title": "New Brunswick",
|
||||
"code": "NB"
|
||||
},
|
||||
"newfoundlandAndLabrador": {
|
||||
"title": "Newfoundland and Labrador",
|
||||
"code": "NL"
|
||||
},
|
||||
"princeEdwardIsland": {
|
||||
"title": "Prince Edward Island",
|
||||
"code": "PE"
|
||||
},
|
||||
"northwestTerritories": {
|
||||
"title": "Northwest Territories",
|
||||
"code": "NT"
|
||||
},
|
||||
"nunavut": {
|
||||
"title": "Nunavut",
|
||||
"code": "NU"
|
||||
},
|
||||
"yukon": {
|
||||
"title": "Yukon",
|
||||
"code": "YT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
cli/examples/react/src/locales/fr/admin.json
Normal file
6
cli/examples/react/src/locales/fr/admin.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"organizations": "Organisations",
|
||||
"reservations": "Réservations"
|
||||
}
|
||||
}
|
239
cli/examples/react/src/locales/fr/common.json
Normal file
239
cli/examples/react/src/locales/fr/common.json
Normal file
@ -0,0 +1,239 @@
|
||||
{
|
||||
"appTitle": "vHub",
|
||||
"fatalError": "Une erreur inattendue est survenue. Veuillez réessayer plus tard.",
|
||||
"navigation": {
|
||||
"admin": {
|
||||
"organizations": "Organisations",
|
||||
"reservations": "Réservations",
|
||||
"title": "Admin"
|
||||
},
|
||||
"owner": {
|
||||
"equipments": "Équipements",
|
||||
"parkingLots": "Cours de stationnement",
|
||||
"reservations": "Réservations",
|
||||
"title": "Propriétaire"
|
||||
},
|
||||
"renter": {
|
||||
"reservations": "Réservations",
|
||||
"title": "Locataire"
|
||||
},
|
||||
"account": {
|
||||
"administration": "Administration",
|
||||
"organization": "Gérer mon organisation"
|
||||
},
|
||||
"logout": "Déconnexion",
|
||||
"login": "Connexion",
|
||||
"switchLanguage": {
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
},
|
||||
"search": "Rechercher"
|
||||
},
|
||||
"languages": {
|
||||
"english": {
|
||||
"title": "Anglais",
|
||||
"code": "en"
|
||||
},
|
||||
"french": {
|
||||
"title": "Français",
|
||||
"code": "fr"
|
||||
}
|
||||
},
|
||||
"mileage": {
|
||||
"KILOMETERS": "km",
|
||||
"MILES": "mi"
|
||||
},
|
||||
"notFound": {
|
||||
"goHome": "Retourner à la page d’accueil",
|
||||
"message": "La page que vous tentiez d’atteindre n’est pas disponible ou n’existe pas.",
|
||||
"title": "Page introuvable"
|
||||
},
|
||||
"reservations": {
|
||||
"activities": {
|
||||
"chronology": {
|
||||
"application": "Heure de l’appareil",
|
||||
"date": "{{date, DD-MM-YYYY, H:mm}}",
|
||||
"synchronization": "Synchronisé à",
|
||||
"title": "Chronologie"
|
||||
},
|
||||
"damages": {
|
||||
"date": "{{date, D MMMM YYYY, H:mm}}",
|
||||
"noDamages": "Aucun dommage déclaré",
|
||||
"title": "Dommages"
|
||||
},
|
||||
"inspection": {
|
||||
"noConditionReports": "Aucun rapport de condition déclaré",
|
||||
"title": "Inspection"
|
||||
},
|
||||
"licencePlate": {
|
||||
"title": "Plaque d’immatriculation"
|
||||
},
|
||||
"location": {
|
||||
"addressTitle": "Position du conducteur",
|
||||
"error": "Impossible de résoudre l’adresse. Réessayez plus tard.",
|
||||
"notFound": "Aucune adresse trouvée à cet emplacement",
|
||||
"notProvided": "Non fourni",
|
||||
"title": "Emplacement"
|
||||
},
|
||||
"odometer": {
|
||||
"notProdived": "Aucune information fournie.",
|
||||
"title": "Odomètre"
|
||||
},
|
||||
"review": {
|
||||
"reasons": {
|
||||
"alreadyPacked": "Pas vide",
|
||||
"dirty": "Intérieur sale",
|
||||
"damaged": "Très endommagé",
|
||||
"snow": "Couverte de neige",
|
||||
"copled": "Couplée ou inaccessible",
|
||||
"notCompliant": "Non conforme pour circuler",
|
||||
"other": "Autre"
|
||||
},
|
||||
"title": "Critique"
|
||||
}
|
||||
},
|
||||
"assignees": {
|
||||
"invitationList": {
|
||||
"empty": "Aucune invitation n’a encore été envoyée"
|
||||
},
|
||||
"sendInvite": {
|
||||
"form": {
|
||||
"actions": {
|
||||
"submit": {
|
||||
"caption": "Envoyer"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"failed": "Impossible d’envoyer un SMS pour le moment, veuillez réessayer plus tard."
|
||||
},
|
||||
"fields": {
|
||||
"driverName": {
|
||||
"errors": {
|
||||
"required": "Vous devez entrer un nom"
|
||||
},
|
||||
"label": "Nom",
|
||||
"placeholder": "Jean Tremblay"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"errors": {
|
||||
"invalid": "Doit respecter le format suivant: +15553219876",
|
||||
"required": "Vous devez entrer un numéro de téléphone"
|
||||
},
|
||||
"label": "Numéro de téléphone",
|
||||
"placeholder": "+15553219876"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stateFilter": {
|
||||
"all": "Toutes",
|
||||
"current": "En cours",
|
||||
"cancelled": "Annulées",
|
||||
"done": "Terminées",
|
||||
"upcoming": "À venir",
|
||||
"late_activation": "Non activées",
|
||||
"late_deactivation": "Non désactivées"
|
||||
},
|
||||
"statusBadge": {
|
||||
"activated": "Activée le {{date, DD\/MM\/YYYY HH:mm}}",
|
||||
"deactivated": "Désactivée le {{date, DD\/MM\/YYYY HH:mm}}",
|
||||
"lateActivation": "Activation en retard {{date, DD\/MM\/YYYY}}",
|
||||
"lateDeactivation": "Désactivation en retard {{date, DD\/MM\/YYYY}}",
|
||||
"upcoming": "À venir"
|
||||
}
|
||||
},
|
||||
"search": "Rechercher",
|
||||
"searchMap": {
|
||||
"departure": "Départ"
|
||||
},
|
||||
"timeAgo": "Il y a {{distance}}",
|
||||
"errors": {
|
||||
"overlap_availabilities": "Les dates sélectionnées chevauchent une autre disponibilité",
|
||||
"overlap_reservations": "Les dates sélectionnées chevauchent une réservation",
|
||||
"equipment_not_available": "L’équipement n'est pas disponible pour ces dates"
|
||||
},
|
||||
"form": {
|
||||
"actions": {
|
||||
"delete": {
|
||||
"caption": "Supprimer"
|
||||
},
|
||||
"cancel": {
|
||||
"caption": "Annuler"
|
||||
}
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"prefixSymbol": "",
|
||||
"suffixSymbol": " $"
|
||||
},
|
||||
"trailerTypes": {
|
||||
"dry_van": "Fourgon sec",
|
||||
"plate_dry_van": "Fourgon sec plate",
|
||||
"storage_van": "Fourgon d'entreposage",
|
||||
"reefer_van": "Fourgon réfrigéré",
|
||||
"heated_van": "Fourgon chauffé",
|
||||
"flatbed": "Plateforme",
|
||||
"dropdeck": "Plateforme surbaissée"
|
||||
},
|
||||
"country": {
|
||||
"canada": {
|
||||
"title": "Canada",
|
||||
"code": "CA",
|
||||
"province": {
|
||||
"ontario": {
|
||||
"title": "Ontario",
|
||||
"code": "ON"
|
||||
},
|
||||
"quebec": {
|
||||
"title": "Québec",
|
||||
"code": "QC"
|
||||
},
|
||||
"britishColumbia": {
|
||||
"title": "Colombie-Britannique",
|
||||
"code": "BC"
|
||||
},
|
||||
"alberta": {
|
||||
"title": "Alberta",
|
||||
"code": "AB"
|
||||
},
|
||||
"manitoba": {
|
||||
"title": "Manitoba",
|
||||
"code": "MB"
|
||||
},
|
||||
"saskatchewan": {
|
||||
"title": "Saskatchewan",
|
||||
"code": "SK"
|
||||
},
|
||||
"novaScotia": {
|
||||
"title": "Nouvelle-Écosse",
|
||||
"code": "NS"
|
||||
},
|
||||
"newBrunswick": {
|
||||
"title": "Nouveau-Brunswick",
|
||||
"code": "NB"
|
||||
},
|
||||
"newfoundlandAndLabrador": {
|
||||
"title": "Terre-Neuve-et-Labrador",
|
||||
"code": "NL"
|
||||
},
|
||||
"princeEdwardIsland": {
|
||||
"title": "Île-du-Prince-Édouard",
|
||||
"code": "PE"
|
||||
},
|
||||
"northwestTerritories": {
|
||||
"title": "Territoires du Nord-Ouest",
|
||||
"code": "NT"
|
||||
},
|
||||
"nunavut": {
|
||||
"title": "Nunavut",
|
||||
"code": "NU"
|
||||
},
|
||||
"yukon": {
|
||||
"title": "Yukon",
|
||||
"code": "YT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3641
cli/package-lock.json
generated
Normal file
3641
cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
73
cli/package.json
Normal file
73
cli/package.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "accent-cli",
|
||||
"version": "0.6.0",
|
||||
"author": "Simon Prévost",
|
||||
"description": "Accent CLI",
|
||||
"bin": {
|
||||
"accent": "./bin/run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "1.5.6",
|
||||
"@oclif/config": "1.9.0",
|
||||
"@oclif/plugin-help": "2.1.4",
|
||||
"@oclif/plugin-not-found": "1.2.2",
|
||||
"@types/decamelize": "1.2.0",
|
||||
"@types/form-data": "2.2.1",
|
||||
"@types/fs-extra": "5.0.1",
|
||||
"@types/glob": "5.0.35",
|
||||
"@types/node-fetch": "1.6.7",
|
||||
"chalk": "2.4.1",
|
||||
"cli-ux": "4.9.3",
|
||||
"decamelize": "2.0.0",
|
||||
"form-data": "2.3.3",
|
||||
"glob": "7.1.3",
|
||||
"node-fetch": "2.3.0",
|
||||
"tslib": "1.9.3",
|
||||
"tslint-config-prettier": "1.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "1.21.0",
|
||||
"@oclif/test": "1.0.1",
|
||||
"@oclif/tslint": "3.1.1",
|
||||
"@types/chai": "4.1.2",
|
||||
"@types/mocha": "5.0.0",
|
||||
"@types/node": "9.6.0",
|
||||
"chai": "4.1.2",
|
||||
"globby": "8.0.1",
|
||||
"mocha": "5.0.5",
|
||||
"prettier": "1.15.3",
|
||||
"ts-node": "7.0.1",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"files": [
|
||||
".oclif.manifest.json",
|
||||
"/bin",
|
||||
"/lib"
|
||||
],
|
||||
"keywords": [
|
||||
"oclif"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"oclif": {
|
||||
"commands": "./lib/commands",
|
||||
"bin": "accent",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-not-found"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf lib && tsc",
|
||||
"clean": "rm -f .oclif.manifest.json",
|
||||
"lint": "tslint --project tsconfig.json 'src/**/*.{ts,tsx}'",
|
||||
"fix": "tslint --project tsconfig.json --fix 'src/**/*.{ts,tsx}'",
|
||||
"prepublishOnly": "npm run build && oclif-dev readme && oclif-dev manifest",
|
||||
"prettier": "prettier --single-quote --no-bracket-spacing --no-semi --write './src/**/*.{js,json,ts,tsx,gql}'"
|
||||
},
|
||||
"types": "lib/index.d.ts"
|
||||
}
|
41
cli/src/base.ts
Normal file
41
cli/src/base.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// Vendor
|
||||
import Command from '@oclif/command'
|
||||
import {error} from '@oclif/errors'
|
||||
import chalk from 'chalk'
|
||||
import cli from 'cli-ux'
|
||||
|
||||
// Services
|
||||
import ConfigFetcher from './services/config'
|
||||
import ProjectFetcher from './services/project-fetcher'
|
||||
|
||||
// Types
|
||||
import {Project} from './types/project'
|
||||
|
||||
const sleep = (ms: number) =>
|
||||
new Promise((resolve: () => void) => setTimeout(resolve, ms))
|
||||
|
||||
export default abstract class extends Command {
|
||||
public projectConfig: ConfigFetcher = new ConfigFetcher()
|
||||
public project?: Project
|
||||
|
||||
public async init() {
|
||||
const config = this.projectConfig.config
|
||||
if (!config.apiUrl) error('You must set an API url in your config')
|
||||
if (!config.apiKey) error('You must set an API key in your config')
|
||||
|
||||
// Fetch project from the GraphQL API.
|
||||
cli.action.start(chalk.white('Fetch config'))
|
||||
await sleep(1000)
|
||||
const fetcher = new ProjectFetcher()
|
||||
this.project = await fetcher.fetch(config)
|
||||
cli.action.stop(chalk.green('✓'))
|
||||
console.log('')
|
||||
}
|
||||
|
||||
public async refreshProject() {
|
||||
const config = this.projectConfig.config
|
||||
|
||||
const fetcher = new ProjectFetcher()
|
||||
this.project = await fetcher.fetch(config)
|
||||
}
|
||||
}
|
55
cli/src/commands/export.ts
Normal file
55
cli/src/commands/export.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// Command
|
||||
import {flags} from '@oclif/command'
|
||||
import Command from '../base'
|
||||
|
||||
// Formatters
|
||||
import ExportFormatter from '../services/formatters/project-export'
|
||||
|
||||
// Services
|
||||
import DocumentPathsFetcher from '../services/document-paths-fetcher'
|
||||
import DocumentExportFormatter from '../services/formatters/document-export'
|
||||
import HookRunner from '../services/hook-runner'
|
||||
|
||||
// Types
|
||||
import {Hooks} from '../types/document-config'
|
||||
|
||||
export default class Export extends Command {
|
||||
public static description =
|
||||
'Export files from Accent and write them to your local filesystem'
|
||||
|
||||
public static examples = [`$ accent export`]
|
||||
|
||||
public static args = []
|
||||
public static flags = {
|
||||
'order-by': flags.string({
|
||||
default: 'index',
|
||||
description: 'Will be used in the export call as the order of the keys',
|
||||
options: ['index', 'key-asc']
|
||||
})
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const {flags} = this.parse(Export)
|
||||
|
||||
const documents = this.projectConfig.files()
|
||||
const formatter = new DocumentExportFormatter()
|
||||
|
||||
// From all the documentConfigs, do the export, write to local file and log the results.
|
||||
new ExportFormatter().log()
|
||||
|
||||
for (const document of documents) {
|
||||
await new HookRunner(document).run(Hooks.beforeExport)
|
||||
|
||||
const targets = new DocumentPathsFetcher().fetch(this.project!, document)
|
||||
|
||||
await Promise.all(
|
||||
targets.map(({path, language, documentPath}) => {
|
||||
formatter.log(path)
|
||||
return document.export(path, language, documentPath, flags)
|
||||
})
|
||||
)
|
||||
|
||||
await new HookRunner(document).run(Hooks.afterExport)
|
||||
}
|
||||
}
|
||||
}
|
57
cli/src/commands/jipt.ts
Normal file
57
cli/src/commands/jipt.ts
Normal file
@ -0,0 +1,57 @@
|
||||
// Command
|
||||
import Command from '../base'
|
||||
|
||||
// Formatters
|
||||
import ExportFormatter from '../services/formatters/project-export'
|
||||
|
||||
// Services
|
||||
import DocumentJiptPathsFetcher from '../services/document-jipt-paths-fetcher'
|
||||
import DocumentExportFormatter from '../services/formatters/document-export'
|
||||
import HookRunner from '../services/hook-runner'
|
||||
|
||||
// Types
|
||||
import {Hooks} from '../types/document-config'
|
||||
|
||||
export default class Jipt extends Command {
|
||||
public static description =
|
||||
'Export jipt files from Accent and write them to your local filesystem'
|
||||
|
||||
public static examples = [`$ accent jipt`]
|
||||
|
||||
public static args = [
|
||||
{
|
||||
description: 'The pseudo language for in-place-translation-editing',
|
||||
name: 'pseudoLanguageName',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
public static flags = {}
|
||||
|
||||
public async run() {
|
||||
const {args} = this.parse(Jipt)
|
||||
const documents = this.projectConfig.files()
|
||||
const formatter = new DocumentExportFormatter()
|
||||
|
||||
// From all the documentConfigs, do the export, write to local file and log the results.
|
||||
new ExportFormatter().log()
|
||||
|
||||
for (const document of documents) {
|
||||
await new HookRunner(document).run(Hooks.beforeExport)
|
||||
|
||||
const targets = new DocumentJiptPathsFetcher().fetch(
|
||||
this.project!,
|
||||
document,
|
||||
args.pseudoLanguageName
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
targets.map(({path, documentPath}) => {
|
||||
formatter.log(path)
|
||||
return document.exportJipt(path, documentPath)
|
||||
})
|
||||
)
|
||||
|
||||
await new HookRunner(document).run(Hooks.afterExport)
|
||||
}
|
||||
}
|
||||
}
|
18
cli/src/commands/stats.ts
Normal file
18
cli/src/commands/stats.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// Command
|
||||
import Command from '../base'
|
||||
|
||||
// Services
|
||||
import Formatter from '../services/formatters/project-stats'
|
||||
|
||||
export default class Stats extends Command {
|
||||
public static description =
|
||||
'Fetch stats from the API and display it beautifully'
|
||||
|
||||
public static examples = [`$ accent stats`]
|
||||
|
||||
public async run() {
|
||||
const formatter = new Formatter(this.project!)
|
||||
|
||||
formatter.log()
|
||||
}
|
||||
}
|
149
cli/src/commands/sync.ts
Normal file
149
cli/src/commands/sync.ts
Normal file
@ -0,0 +1,149 @@
|
||||
// Vendor
|
||||
import {flags} from '@oclif/command'
|
||||
import {existsSync} from 'fs'
|
||||
|
||||
// Command
|
||||
import Command from '../base'
|
||||
|
||||
// Formatters
|
||||
import AddTranslationsFormatter from '../services/formatters/project-add-translations'
|
||||
import ExportFormatter from '../services/formatters/project-export'
|
||||
import SyncFormatter from '../services/formatters/project-sync'
|
||||
|
||||
// Services
|
||||
import Document from '../services/document'
|
||||
import DocumentPathsFetcher from '../services/document-paths-fetcher'
|
||||
import CommitOperationFormatter from '../services/formatters/commit-operation'
|
||||
import DocumentExportFormatter from '../services/formatters/document-export'
|
||||
import HookRunner from '../services/hook-runner'
|
||||
|
||||
// Types
|
||||
import {Hooks} from '../types/document-config'
|
||||
|
||||
export default class Sync extends Command {
|
||||
public static description =
|
||||
'Sync files in Accent and write them to your local filesystem'
|
||||
|
||||
public static examples = [`$ accent sync`]
|
||||
|
||||
public static args = []
|
||||
|
||||
public static flags = {
|
||||
'add-translations': flags.boolean({
|
||||
description:
|
||||
'Add translations in Accent to help translators if you already have translated strings'
|
||||
}),
|
||||
'merge-type': flags.string({
|
||||
default: 'smart',
|
||||
description:
|
||||
'Will be used in the add translations call as the "merge_type" param',
|
||||
options: ['smart', 'passive', 'force']
|
||||
}),
|
||||
'order-by': flags.string({
|
||||
default: 'index',
|
||||
description: 'Will be used in the export call as the order of the keys',
|
||||
options: ['index', 'key-asc']
|
||||
}),
|
||||
'sync-type': flags.string({
|
||||
default: 'smart',
|
||||
description: 'Will be used in the sync call as the "sync_type" param',
|
||||
options: ['smart', 'passive']
|
||||
}),
|
||||
write: flags.boolean({
|
||||
description: 'Write the file from the export _after_ the operation'
|
||||
})
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const {flags} = this.parse(Sync)
|
||||
|
||||
const documents = this.projectConfig.files()
|
||||
|
||||
// From all the documentConfigs, do the sync or peek operations and log the results.
|
||||
new SyncFormatter().log()
|
||||
|
||||
for (const document of documents) {
|
||||
await new HookRunner(document).run(Hooks.beforeSync)
|
||||
|
||||
await Promise.all(this.syncDocumentConfig(document))
|
||||
|
||||
await new HookRunner(document).run(Hooks.afterSync)
|
||||
}
|
||||
|
||||
if (flags['add-translations']) {
|
||||
new AddTranslationsFormatter().log()
|
||||
|
||||
for (const document of documents) {
|
||||
await new HookRunner(document).run(Hooks.beforeAddTranslations)
|
||||
|
||||
await Promise.all(this.addTranslationsDocumentConfig(document))
|
||||
|
||||
await new HookRunner(document).run(Hooks.afterAddTranslations)
|
||||
}
|
||||
}
|
||||
|
||||
if (!flags.write) return
|
||||
// After syncing the files in Accent, the list of documents could have changed.
|
||||
await this.refreshProject()
|
||||
|
||||
const formatter = new DocumentExportFormatter()
|
||||
|
||||
// From all the documentConfigs, do the export, write to local file and log the results.
|
||||
new ExportFormatter().log()
|
||||
|
||||
for (const document of documents) {
|
||||
await new HookRunner(document).run(Hooks.beforeExport)
|
||||
|
||||
const targets = new DocumentPathsFetcher().fetch(this.project!, document)
|
||||
|
||||
await Promise.all(
|
||||
targets.map(({path, language, documentPath}) => {
|
||||
formatter.log(path)
|
||||
return document.export(path, language, documentPath, flags)
|
||||
})
|
||||
)
|
||||
|
||||
await new HookRunner(document).run(Hooks.afterExport)
|
||||
}
|
||||
}
|
||||
|
||||
private syncDocumentConfig(document: Document) {
|
||||
const {flags} = this.parse(Sync)
|
||||
const formatter = new CommitOperationFormatter()
|
||||
|
||||
return document.paths.map(async path => {
|
||||
const operations = await document.sync(path, flags)
|
||||
|
||||
if (operations.sync && !operations.peek) formatter.logSync(path)
|
||||
if (operations.peek) formatter.logPeek(path, operations.peek)
|
||||
|
||||
return operations
|
||||
})
|
||||
}
|
||||
|
||||
private addTranslationsDocumentConfig(document: Document) {
|
||||
const {flags} = this.parse(Sync)
|
||||
const formatter = new CommitOperationFormatter()
|
||||
|
||||
const targets = new DocumentPathsFetcher()
|
||||
.fetch(this.project!, document)
|
||||
.filter(({language}) => language !== document.config.language)
|
||||
.filter(({path}) => existsSync(path))
|
||||
|
||||
return targets.map(async ({path, language, documentPath}) => {
|
||||
const operations = await document.addTranslations(
|
||||
path,
|
||||
language,
|
||||
documentPath,
|
||||
flags
|
||||
)
|
||||
|
||||
if (operations.addTranslations && !operations.peek) {
|
||||
formatter.logAddTranslations(path)
|
||||
}
|
||||
if (operations.peek) formatter.logPeek(path, operations.peek)
|
||||
|
||||
return operations
|
||||
})
|
||||
}
|
||||
}
|
1
cli/src/index.ts
Normal file
1
cli/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {run} from '@oclif/command'
|
41
cli/src/services/config.ts
Normal file
41
cli/src/services/config.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// Vendor
|
||||
import {error} from '@oclif/errors'
|
||||
import * as fs from 'fs-extra'
|
||||
|
||||
// Services
|
||||
import Document from './document'
|
||||
|
||||
// Types
|
||||
import {Config} from '../types/config'
|
||||
|
||||
export default class ConfigFetcher {
|
||||
public readonly config: Config
|
||||
|
||||
constructor() {
|
||||
this.config = fs.readJsonSync('accent.json')
|
||||
this.config.apiKey = this.config.apiKey || process.env.ACCENT_API_KEY!
|
||||
this.config.apiUrl = this.config.apiUrl || process.env.ACCENT_API_URL!
|
||||
|
||||
if (!this.config.apiKey) {
|
||||
error(
|
||||
'You must have an apiKey key in the config or the ACCENT_API_KEY environment variable'
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.config.apiUrl) {
|
||||
error(
|
||||
'You must have an apiUrl key in the config or the ACCENT_API_URL environment variable'
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.config.files) {
|
||||
error('You must have at least 1 document set in your config')
|
||||
}
|
||||
}
|
||||
|
||||
public files(): Document[] {
|
||||
return this.config.files.map(
|
||||
documentConfig => new Document(documentConfig, this.config)
|
||||
)
|
||||
}
|
||||
}
|
26
cli/src/services/document-jipt-paths-fetcher.ts
Normal file
26
cli/src/services/document-jipt-paths-fetcher.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// Types
|
||||
import {DocumentPath} from '../types/document-path'
|
||||
import {Project} from '../types/project'
|
||||
import Document from './document'
|
||||
|
||||
export default class DocumentJiptPathsFetcher {
|
||||
public fetch(
|
||||
project: Project,
|
||||
document: Document,
|
||||
pseudoLanguageName: string
|
||||
): DocumentPath[] {
|
||||
return project.documents.entries
|
||||
.map(({path}) => path)
|
||||
.map(path => {
|
||||
const parsedTarget = document.target
|
||||
.replace('%slug%', pseudoLanguageName)
|
||||
.replace('%original_file_name%', path)
|
||||
|
||||
return {
|
||||
documentPath: path,
|
||||
language: pseudoLanguageName,
|
||||
path: parsedTarget
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
22
cli/src/services/document-paths-fetcher.ts
Normal file
22
cli/src/services/document-paths-fetcher.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// Types
|
||||
import {DocumentPath} from '../types/document-path'
|
||||
import {Project} from '../types/project'
|
||||
import Document from './document'
|
||||
|
||||
export default class DocumentPathsFetcher {
|
||||
public fetch(project: Project, document: Document): DocumentPath[] {
|
||||
const languageSlugs = project.revisions.map(({language}) => language.slug)
|
||||
const documentPaths = project.documents.entries.map(({path}) => path)
|
||||
return languageSlugs.reduce((memo: DocumentPath[], slug) => {
|
||||
documentPaths.forEach(path => {
|
||||
const parsedTarget = document.target
|
||||
.replace('%slug%', slug)
|
||||
.replace('%original_file_name%', path)
|
||||
|
||||
memo.push({documentPath: path, path: parsedTarget, language: slug})
|
||||
})
|
||||
|
||||
return memo
|
||||
}, [])
|
||||
}
|
||||
}
|
162
cli/src/services/document.ts
Normal file
162
cli/src/services/document.ts
Normal file
@ -0,0 +1,162 @@
|
||||
// Vendor
|
||||
import * as FormData from 'form-data'
|
||||
import * as fs from 'fs-extra'
|
||||
import fetch, {Response} from 'node-fetch'
|
||||
import * as path from 'path'
|
||||
|
||||
// Services
|
||||
import Tree from './tree'
|
||||
|
||||
// Types
|
||||
import {Config} from '../types/config'
|
||||
import {DocumentConfig} from '../types/document-config'
|
||||
import {OperationResponse} from '../types/operation-response'
|
||||
|
||||
const enum OperationName {
|
||||
Sync = 'sync',
|
||||
AddTranslation = 'addTranslations'
|
||||
}
|
||||
|
||||
export default class Document {
|
||||
public paths: string[]
|
||||
public readonly apiKey: string
|
||||
public readonly apiUrl: string
|
||||
public readonly config: DocumentConfig
|
||||
public readonly target: string
|
||||
|
||||
constructor(documentConfig: DocumentConfig, config: Config) {
|
||||
this.config = documentConfig
|
||||
this.apiKey = config.apiKey
|
||||
this.apiUrl = config.apiUrl
|
||||
this.target = this.config.target
|
||||
this.paths = new Tree(this.config).list()
|
||||
}
|
||||
|
||||
public refreshPaths() {
|
||||
this.paths = new Tree(this.config).list()
|
||||
}
|
||||
|
||||
public async sync(file: string, options: any) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', fs.createReadStream(file))
|
||||
formData.append('document_path', this.parseDocumentName(file))
|
||||
formData.append('document_format', this.config.format)
|
||||
formData.append('language', this.config.language)
|
||||
|
||||
let url = `${this.apiUrl}/sync`
|
||||
if (!options.write) url = `${url}/peek`
|
||||
if (options['sync-type']) formData.append('sync_type', options['sync-type'])
|
||||
|
||||
const response = await fetch(url, {
|
||||
body: formData,
|
||||
headers: this.authorizationHeader(),
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
return this.handleResponse(response, options, OperationName.Sync)
|
||||
}
|
||||
|
||||
public async addTranslations(
|
||||
file: string,
|
||||
language: string,
|
||||
documentPath: string,
|
||||
options: any
|
||||
) {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('file', fs.createReadStream(file))
|
||||
formData.append('document_path', documentPath)
|
||||
formData.append('document_format', this.config.format)
|
||||
formData.append('language', language)
|
||||
|
||||
let url = `${this.apiUrl}/add-translations`
|
||||
if (!options.write) url = `${url}/peek`
|
||||
if (options['merge-type']) {
|
||||
formData.append('merge_type', options['merge-type'])
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
body: formData,
|
||||
headers: this.authorizationHeader(),
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
return this.handleResponse(response, options, OperationName.AddTranslation)
|
||||
}
|
||||
|
||||
public async export(
|
||||
file: string,
|
||||
language: string,
|
||||
documentPath: string,
|
||||
options: any
|
||||
) {
|
||||
language = language || this.config.language
|
||||
|
||||
const query = [
|
||||
['document_path', documentPath],
|
||||
['document_format', this.config.format],
|
||||
['order_by', options['order-by']],
|
||||
['language', language]
|
||||
]
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join('&')
|
||||
|
||||
const url = `${this.apiUrl}/export?${query}`
|
||||
const response = await fetch(url, {
|
||||
headers: this.authorizationHeader()
|
||||
})
|
||||
|
||||
return this.writeResponseToFile(response, file)
|
||||
}
|
||||
|
||||
public async exportJipt(file: string, documentPath: string) {
|
||||
const query = [
|
||||
['document_path', documentPath],
|
||||
['document_format', this.config.format]
|
||||
]
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join('&')
|
||||
|
||||
const url = `${this.apiUrl}/jipt-export?${query}`
|
||||
const response = await fetch(url, {
|
||||
headers: this.authorizationHeader()
|
||||
})
|
||||
|
||||
return this.writeResponseToFile(response, file)
|
||||
}
|
||||
|
||||
private authorizationHeader() {
|
||||
return {authorization: `Bearer ${this.apiKey}`}
|
||||
}
|
||||
|
||||
private parseDocumentName(file: string): string {
|
||||
return path.basename(file).replace(path.extname(file), '')
|
||||
}
|
||||
|
||||
private writeResponseToFile(response: Response, file: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(file, {autoClose: true})
|
||||
response.body.pipe(fileStream)
|
||||
response.body.on('error', reject)
|
||||
fileStream.on('finish', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
private async handleResponse(
|
||||
response: Response,
|
||||
options: any,
|
||||
operationName: OperationName
|
||||
): Promise<OperationResponse> {
|
||||
if (options.write) {
|
||||
if (response.status >= 400) {
|
||||
return {[operationName]: {success: false}, peek: false}
|
||||
}
|
||||
|
||||
return {[operationName]: {success: true}, peek: false}
|
||||
} else {
|
||||
const {data} = await response.json()
|
||||
|
||||
return {peek: data, [operationName]: {success: true}}
|
||||
}
|
||||
}
|
||||
}
|
55
cli/src/services/formatters/commit-operation.ts
Normal file
55
cli/src/services/formatters/commit-operation.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
// Types
|
||||
import {PeekOperation} from '../../types/operation'
|
||||
|
||||
// Constants
|
||||
const MASTER_ONLY_ACTIONS = ['new', 'renew', 'remove']
|
||||
|
||||
export default class CommitOperationFormatter {
|
||||
public logSync(path: string) {
|
||||
console.log(' ', chalk.white(path))
|
||||
console.log(' ', chalk.green('✓ Successfully synced the files in Accent'))
|
||||
console.log('')
|
||||
}
|
||||
|
||||
public logAddTranslations(path: string) {
|
||||
console.log(' ', chalk.white(path))
|
||||
console.log(' ', chalk.green('✓ Successfully add translations in Accent'))
|
||||
console.log('')
|
||||
}
|
||||
|
||||
public logPeek(path: string, operations: PeekOperation) {
|
||||
console.log(' ', chalk.white(path))
|
||||
|
||||
if (!Object.keys(operations.stats).length) {
|
||||
console.log(' ', chalk.gray('~~ No changes for this file ~~'))
|
||||
}
|
||||
|
||||
Object.entries(operations.stats).map((stat, index) => {
|
||||
let actions = Object.entries(stat[1])
|
||||
if (index > 0) {
|
||||
actions = actions.filter(
|
||||
([action]) => !MASTER_ONLY_ACTIONS.includes(action)
|
||||
)
|
||||
}
|
||||
|
||||
actions.map(([action, name]) => {
|
||||
console.log(
|
||||
' ',
|
||||
chalk.bold(this.formatAction(action)),
|
||||
':',
|
||||
chalk.bold.white(name)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
console.log('')
|
||||
}
|
||||
|
||||
private formatAction(action: string) {
|
||||
const capitalized = action.charAt(0).toUpperCase() + action.slice(1)
|
||||
return capitalized.replace(/_/g, ' ')
|
||||
}
|
||||
}
|
13
cli/src/services/formatters/document-export.ts
Normal file
13
cli/src/services/formatters/document-export.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
export default class DocumentExportFormatter {
|
||||
public log(path: string) {
|
||||
console.log(' ', chalk.white(path))
|
||||
console.log(
|
||||
' ',
|
||||
chalk.green('✓ Successfully write the locale files from Accent')
|
||||
)
|
||||
console.log('')
|
||||
}
|
||||
}
|
17
cli/src/services/formatters/hook-runner.ts
Normal file
17
cli/src/services/formatters/hook-runner.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
import * as decamelize from 'decamelize'
|
||||
|
||||
const capitalizeFirstLetter = (str: string) =>
|
||||
str.charAt(0).toUpperCase() + str.slice(1)
|
||||
|
||||
export default class HookRunnerFomatter {
|
||||
public log(name: string, commands: string[]) {
|
||||
const operation = capitalizeFirstLetter(decamelize(name, ' '))
|
||||
console.log(chalk.yellow('➤ '), chalk.bold(chalk.yellow(`${operation}:`)))
|
||||
commands.forEach(command => {
|
||||
console.log(' ', chalk.yellow(command))
|
||||
})
|
||||
console.log('')
|
||||
}
|
||||
}
|
10
cli/src/services/formatters/project-add-translations.ts
Normal file
10
cli/src/services/formatters/project-add-translations.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
export default class ProjectAddTranslationsFormatter {
|
||||
public log() {
|
||||
console.log(chalk.magenta('Adding translations paths'))
|
||||
|
||||
console.log('')
|
||||
}
|
||||
}
|
10
cli/src/services/formatters/project-export.ts
Normal file
10
cli/src/services/formatters/project-export.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
export default class ProjectExportFormatter {
|
||||
public log() {
|
||||
console.log(chalk.magenta('Writing files'))
|
||||
|
||||
console.log('')
|
||||
}
|
||||
}
|
84
cli/src/services/formatters/project-stats.ts
Normal file
84
cli/src/services/formatters/project-stats.ts
Normal file
@ -0,0 +1,84 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
// Types
|
||||
import {Document, Project, Revision} from '../../types/project'
|
||||
|
||||
export default class ProjectStatsFormatter {
|
||||
private readonly project: Project
|
||||
|
||||
constructor(project: Project) {
|
||||
this.project = project
|
||||
}
|
||||
|
||||
public log() {
|
||||
const translationsCount = this.project.revisions.reduce(
|
||||
(memo, revision: Revision) => memo + revision.translationsCount,
|
||||
0
|
||||
)
|
||||
const conflictsCount = this.project.revisions.reduce(
|
||||
(memo, revision: Revision) => memo + revision.conflictsCount,
|
||||
0
|
||||
)
|
||||
const reviewedCount = this.project.revisions.reduce(
|
||||
(memo, revision: Revision) => memo + revision.reviewedCount,
|
||||
0
|
||||
)
|
||||
|
||||
console.log(chalk.magenta('Last synced'))
|
||||
console.log(' ', chalk.white.bold(this.project.lastSyncedAt))
|
||||
|
||||
console.log('')
|
||||
|
||||
console.log(chalk.magenta('Master language'))
|
||||
console.log(
|
||||
' ',
|
||||
chalk.white.bold(this.project.language.name) +
|
||||
' – ' +
|
||||
this.project.language.slug
|
||||
)
|
||||
|
||||
console.log('')
|
||||
|
||||
if (this.project.revisions.length > 1) {
|
||||
console.log(
|
||||
chalk.magenta(`Translations (${this.project.revisions.length - 1})`)
|
||||
)
|
||||
this.project.revisions.forEach((revision: Revision) => {
|
||||
if (this.project.language.id !== revision.language.id) {
|
||||
console.log(
|
||||
' ',
|
||||
chalk.white.bold(revision.language.name) +
|
||||
' – ' +
|
||||
revision.language.slug
|
||||
)
|
||||
console.log('')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log(chalk.magenta('Documents'))
|
||||
this.project.documents.entries.forEach((document: Document) => {
|
||||
console.log(
|
||||
' ',
|
||||
chalk.gray('Format:'),
|
||||
chalk.white.bold(document.format)
|
||||
)
|
||||
console.log(' ', chalk.gray('Path:'), chalk.white.bold(document.path))
|
||||
console.log('')
|
||||
})
|
||||
|
||||
console.log(chalk.magenta('Strings'))
|
||||
console.log(
|
||||
' ',
|
||||
chalk.white('# Strings:'),
|
||||
chalk.white(`${translationsCount}`)
|
||||
)
|
||||
console.log(
|
||||
' ',
|
||||
chalk.green('✓ Reviewed:'),
|
||||
chalk.green(`${reviewedCount}`)
|
||||
)
|
||||
console.log(' ', chalk.red('× In review:'), chalk.red(`${conflictsCount}`))
|
||||
}
|
||||
}
|
10
cli/src/services/formatters/project-sync.ts
Normal file
10
cli/src/services/formatters/project-sync.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// Vendor
|
||||
import chalk from 'chalk'
|
||||
|
||||
export default class ProjectSyncFormatter {
|
||||
public log() {
|
||||
console.log(chalk.magenta('Syncing sources'))
|
||||
|
||||
console.log('')
|
||||
}
|
||||
}
|
32
cli/src/services/hook-runner.ts
Normal file
32
cli/src/services/hook-runner.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// Vendor
|
||||
import {execSync} from 'child_process'
|
||||
|
||||
// Formatters
|
||||
import Formatter from './formatters/hook-runner'
|
||||
|
||||
// Types
|
||||
import {HookConfig, Hooks} from '../types/document-config'
|
||||
import Document from './document'
|
||||
|
||||
export default class HookRunner {
|
||||
public readonly hooks?: HookConfig
|
||||
private readonly document: Document
|
||||
|
||||
constructor(document: Document) {
|
||||
this.document = document
|
||||
this.hooks = document.config.hooks
|
||||
}
|
||||
|
||||
public async run(name: Hooks) {
|
||||
if (!this.hooks) return null
|
||||
const hooks = this.hooks[name]
|
||||
|
||||
if (hooks) {
|
||||
new Formatter().log(name, hooks)
|
||||
|
||||
hooks.forEach(execSync)
|
||||
}
|
||||
|
||||
return this.document.refreshPaths()
|
||||
}
|
||||
}
|
65
cli/src/services/project-fetcher.ts
Normal file
65
cli/src/services/project-fetcher.ts
Normal file
@ -0,0 +1,65 @@
|
||||
// Vendor
|
||||
import {error} from '@oclif/errors'
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
// Types
|
||||
import {Config} from '../types/config'
|
||||
import {Project} from '../types/project'
|
||||
|
||||
export default class ProjectFetcher {
|
||||
public async fetch(config: Config): Promise<Project> {
|
||||
const response = await this.graphql(config)
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
error(`Can not find the project for the key: ${config.apiKey}`)
|
||||
}
|
||||
|
||||
return data.data && data.data.viewer.project
|
||||
}
|
||||
|
||||
private graphql(config: Config) {
|
||||
const query = `query ProjectDetails($project_id: ID!) {
|
||||
viewer {
|
||||
project(id: $project_id) {
|
||||
id
|
||||
name
|
||||
lastSyncedAt
|
||||
|
||||
language {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
documents {
|
||||
entries {
|
||||
id
|
||||
path
|
||||
format
|
||||
}
|
||||
}
|
||||
revisions {
|
||||
id
|
||||
translationsCount
|
||||
conflictsCount
|
||||
reviewedCount
|
||||
language {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return fetch(`${config.apiUrl}/graphql`, {
|
||||
body: JSON.stringify({query}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${config.apiKey}`
|
||||
},
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
}
|
17
cli/src/services/tree.ts
Normal file
17
cli/src/services/tree.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// Vendor
|
||||
import * as glob from 'glob'
|
||||
|
||||
// Types
|
||||
import {DocumentConfig} from '../types/document-config'
|
||||
|
||||
export default class Tree {
|
||||
private readonly document: DocumentConfig
|
||||
|
||||
constructor(document: DocumentConfig) {
|
||||
this.document = document
|
||||
}
|
||||
|
||||
public list(): string[] {
|
||||
return glob.sync(this.document.source, {})
|
||||
}
|
||||
}
|
8
cli/src/types/config.ts
Normal file
8
cli/src/types/config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// Types
|
||||
import {DocumentConfig} from './document-config'
|
||||
|
||||
export interface Config {
|
||||
apiUrl: string
|
||||
apiKey: string
|
||||
files: DocumentConfig[]
|
||||
}
|
26
cli/src/types/document-config.ts
Normal file
26
cli/src/types/document-config.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export enum Hooks {
|
||||
beforeAddTranslations = 'beforeAddTranslations',
|
||||
afterAddTranslations = 'afterAddTranslations',
|
||||
beforeExport = 'beforeExport',
|
||||
afterExport = 'afterExport',
|
||||
beforeSync = 'beforeSync',
|
||||
afterSync = 'afterSync'
|
||||
}
|
||||
|
||||
export interface HookConfig {
|
||||
[Hooks.beforeAddTranslations]: string[]
|
||||
[Hooks.afterAddTranslations]: string[]
|
||||
[Hooks.beforeExport]: string[]
|
||||
[Hooks.afterExport]: string[]
|
||||
[Hooks.beforeSync]: string[]
|
||||
[Hooks.afterSync]: string[]
|
||||
}
|
||||
|
||||
export interface DocumentConfig {
|
||||
name: string
|
||||
language: string
|
||||
format: string
|
||||
source: string
|
||||
target: string
|
||||
hooks?: HookConfig
|
||||
}
|
5
cli/src/types/document-path.ts
Normal file
5
cli/src/types/document-path.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface DocumentPath {
|
||||
path: string
|
||||
language: string
|
||||
documentPath: string
|
||||
}
|
4
cli/src/types/operation-response.ts
Normal file
4
cli/src/types/operation-response.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface OperationResponse {
|
||||
peek: any
|
||||
[x: string]: any
|
||||
}
|
4
cli/src/types/operation.ts
Normal file
4
cli/src/types/operation.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface PeekOperation {
|
||||
operations: any[]
|
||||
stats: any
|
||||
}
|
31
cli/src/types/project.ts
Normal file
31
cli/src/types/project.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export interface Language {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
export interface Revision {
|
||||
id: string
|
||||
language: Language
|
||||
translationsCount: number
|
||||
conflictsCount: number
|
||||
reviewedCount: number
|
||||
}
|
||||
|
||||
export interface Document {
|
||||
path: string
|
||||
format: string
|
||||
}
|
||||
|
||||
export interface PaginatedDocuments {
|
||||
entries: Document[]
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string
|
||||
name: string
|
||||
lastSyncedAt: string
|
||||
language: Language
|
||||
revisions: Revision[]
|
||||
documents: PaginatedDocuments
|
||||
}
|
29
cli/tsconfig.json
Normal file
29
cli/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"allowUnreachableCode": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
"pretty": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noEmit": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"rootDirs": [
|
||||
"./src"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2017"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/**/*"
|
||||
]
|
||||
}
|
21
cli/tslint.json
Normal file
21
cli/tslint.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": ["@oclif/tslint", "tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"no-implicit-dependencies": false,
|
||||
"no-submodule-imports": false,
|
||||
"no-console": false,
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-leading-underscore",
|
||||
"allow-pascal-case"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"no-empty-interface": false,
|
||||
"interface-name": false,
|
||||
"no-shadowed-variable": false,
|
||||
"curly": [true, "ignore-same-line"],
|
||||
"no-empty": false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user