Browse Source

Add i18n

master
Dashie der otter 6 months ago
parent
commit
bbac706220
Signed by: dashie <rhaamo@leloop.org> GPG Key ID: C2D57B325840B755
14 changed files with 267 additions and 25 deletions
  1. +2
    -0
      package.json
  2. +1
    -1
      public/index.html
  3. +10
    -0
      src/App.scss
  4. +14
    -8
      src/App.vue
  5. +21
    -5
      src/background.js
  6. +6
    -6
      src/components/TaskItem.vue
  7. +30
    -0
      src/i18n/en-US/index.js
  8. +30
    -0
      src/i18n/fr-FR/index.js
  9. +20
    -0
      src/i18n/index.js
  10. +2
    -0
      src/main.js
  11. +6
    -2
      src/utils/time.js
  12. +19
    -1
      src/views/About.vue
  13. +42
    -1
      src/views/Settings.vue
  14. +64
    -1
      yarn.lock

+ 2
- 0
package.json View File

@@ -15,6 +15,7 @@
"dependencies": {
"bootstrap-vue": "^2.15.0",
"core-js": "^3.6.5",
"electron-store": "^6.0.0",
"fork-awesome": "^1.1.7",
"knex": "^0.21.2",
"moment": "^2.27.0",
@@ -23,6 +24,7 @@
"sass-loader": "^9.0.2",
"sqlite3": "^5.0.0",
"vue": "^2.6.11",
"vue-i18n": "^8.18.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},


+ 1
- 1
public/index.html View File

@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<title>BrainClock</title>
</head>
<body>
<noscript>


+ 10
- 0
src/App.scss View File

@@ -34,4 +34,14 @@ div.taskTitle blockquote {
font-size: 1rem !important;
border-left: 3px solid lightgrey;
padding-left: 5px;
}

/* Settings */
div.settings {
margin-top: 1em;
}

/* About */
div.about {
margin-top: 1em;
}

+ 14
- 8
src/App.vue View File

@@ -7,8 +7,8 @@
<textarea v-model="taskText" name="taskText" id="taskText" cols="20" rows="3" placeholder="OwO ?"></textarea>
</b-col>
<b-col>
Started: <span id="timerStartDate">{{taskStartedAt}}</span><br />
Spent: <span id="timerSpentTime">{{taskTimeSpent}}</span>
{{ $t("header.started") }} <span id="timerStartDate">{{taskStartedAt}}</span><br />
{{ $t("header.spent") }} <span id="timerSpentTime">{{taskTimeSpent}}</span>
</b-col>
<b-col cols="2">
<b-button v-on:click="toggleTimer" variant="primary" id="btTimer"><i :class="timer.icon" aria-hidden="true"></i></b-button>
@@ -19,9 +19,9 @@

<div id="nav">
<b-nav tabs justified>
<b-nav-item :active='$route.name =="home"' :to="{ name: 'home' }">Tasks</b-nav-item>
<b-nav-item :active='$route.name =="settings"' :to="{ name: 'settings' }">Settings</b-nav-item>
<b-nav-item :active='$route.name =="about"' :to="{ name: 'about' }">About</b-nav-item>
<b-nav-item :active='$route.name =="home"' :to="{ name: 'home' }">{{ $t("nav.tasks") }}</b-nav-item>
<b-nav-item :active='$route.name =="settings"' :to="{ name: 'settings' }">{{ $t("nav.settings") }}</b-nav-item>
<b-nav-item :active='$route.name =="about"' :to="{ name: 'about' }">{{ $t("nav.about") }}</b-nav-item>
</b-nav>
</div>

@@ -43,11 +43,14 @@ export default {
spent: null,
icon: 'fa fa-play'
},
timerInterval: null
timerInterval: null,
preferences: {
locale: 'en-US'
}
}),
computed: {
taskStartedAt() { return this.timer.startedAt === null ? 'Not yet.' : timeUtils.formatShort(this.timer.startedAt) },
taskTimeSpent() { return this.timer.spent === null ? '0ns.' : timeUtils.secondsToDdHhMmSs(this.timer.spent) }
taskStartedAt() { return this.timer.startedAt === null ? this.$t("header.time.notYet") : timeUtils.formatShort(this.timer.startedAt, this.$i18n.locale) },
taskTimeSpent() { return this.timer.spent === null ? this.$t("header.time.oNs") : timeUtils.secondsToDdHhMmSs(this.timer.spent, this.$i18n.locale) }
},
methods: {
toggleTimer: function () {
@@ -81,6 +84,9 @@ export default {
this.taskText = ''
clearInterval(this.timerInterval)
});

// Get various preferences
this.$root.$i18n.locale = this.preferences.locale = window.ipcRenderer.sendSync('getPreference', {key: 'locale'})
},

}


+ 21
- 5
src/background.js View File

@@ -1,11 +1,23 @@
'use strict'

import { app, protocol, BrowserWindow, ipcMain } from 'electron'
import { app, protocol, BrowserWindow, ipcMain, shell } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import ElectronStore from 'electron-store'

const path = require('path')
const isDevelopment = process.env.NODE_ENV !== 'production'

const preferencesSchema = {
locale: {
type: 'string'
}
}
const preferencesStore = new ElectronStore({
preferencesSchema,
name: 'settings'
});

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
@@ -154,12 +166,12 @@ function insertTaskAndNotify (start, end, text) {
})
}

ipcMain.on('getPreference', (event, key) => {
event.returnValue = win.get(key);
ipcMain.on('getPreference', (event, {key}) => {
event.returnValue = preferencesStore.get(key);
})

ipcMain.on('setPreference', (event, key, value) => {
event.returnValue = win.set(key, value);
ipcMain.on('setPreference', (event, {key, value}) => {
event.returnValue = preferencesStore.set(key, value);
})

ipcMain.on('getAllTasks', (event) => {
@@ -173,4 +185,8 @@ ipcMain.on('deleteTask', (event, taskId) => {
win.webContents.send("taskRemoved", {taskId})
}
})
})

ipcMain.on('openAboutLink', (_) => {
shell.openExternal('https://dev.sigpipe.me/dashie/brainclock')
})

+ 6
- 6
src/components/TaskItem.vue View File

@@ -1,16 +1,16 @@
<template>
<div class="taskItem" :data-task-id="task.id">
<div class="taskTimes">
<span class="taskSpent">{{ taskDuration }}</span> <span class="taskSpan pull-right">{{ taskStartedAt }} to {{ taskEndedAt }}</span>
<span class="taskSpent">{{ taskDuration }}</span> <span class="taskSpan pull-right">{{ taskStartedAt }} {{ $t('tasks.to') }} {{ taskEndedAt }}</span>
</div>
<b-row>
<b-col class="taskTitle">
<blockquote class="blockquote font-weight-light">
{{ task.title || 'No task description.' }}
{{ task.title || $t('tasks.noDescription') }}
</blockquote>
</b-col>
<b-col class="taskActions text-right" cols="2">
<b-button v-on:click="deleteTask(task.id)" variant="outline-danger" size="sm"><i class="fa fa-remove" aria-hidden="true"></i></b-button>
<b-button v-on:click="deleteTask(task.id)" :title="$t('tasks.removeThisTask')" variant="outline-danger" size="sm"><i class="fa fa-remove" aria-hidden="true"></i></b-button>
</b-col>
</b-row>
</div>
@@ -25,9 +25,9 @@ export default {
task: Object
},
computed: {
taskStartedAt() { return timeUtils.formatShort(this.task.started) },
taskEndedAt() { return timeUtils.formatShort(this.task.ended) },
taskDuration() { return timeUtils.secondsToDdHhMmSs(this.task.duration) }
taskStartedAt() { return timeUtils.formatShort(this.task.started, this.$i18n.locale) },
taskEndedAt() { return timeUtils.formatShort(this.task.ended, this.$i18n.locale) },
taskDuration() { return timeUtils.secondsToDdHhMmSs(this.task.duration, this.$i18n.locale) }
},
methods: {
deleteTask: function (taskId) {


+ 30
- 0
src/i18n/en-US/index.js View File

@@ -0,0 +1,30 @@
export default {
settings: {
form: {
locale: 'Locale:',
save: 'save'
}
},
header: {
started: 'Started:',
spent: 'Spent:',
time: {
notYet: 'Not yet.',
oNs: '0ns.'
}
},
nav: {
tasks: 'Tasks',
settings: 'Settings',
about: 'About'
},
tasks: {
noDescription: 'This task has no description.',
to: 'to',
removeThisTask: 'Remove this task.'
},
about: {
description: 'BrainClock is developped by dashie.',
sources: 'sources'
}
}

+ 30
- 0
src/i18n/fr-FR/index.js View File

@@ -0,0 +1,30 @@
export default {
settings: {
form: {
locale: 'Locale:',
save: 'sauver'
}
},
header: {
started: 'Démarré:',
spent: 'Écoulé:',
time: {
notYet: 'Pas encore.',
oNs: '0ns.'
}
},
nav: {
tasks: 'Tâches',
settings: 'Paramètres',
about: 'À propos'
},
tasks: {
noDescription: "Cette tâche n'a pas de déscription.",
to: 'to',
removeThisTask: 'Supprimer cette tâche.'
},
about: {
description: 'BrainClock est développé par dashie.',
sources: 'sources'
}
}

+ 20
- 0
src/i18n/index.js View File

@@ -0,0 +1,20 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'

import enUS from './en-US'
import frFR from './fr-FR'

Vue.use(VueI18n)

const messages = {
'en-US': enUS,
'fr-FR': frFR,
}

const i18n = new VueI18n({
locale: 'en-US',
fallbackLocale: 'en-US',
messages,
})

export default i18n

+ 2
- 0
src/main.js View File

@@ -2,6 +2,7 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './i18n'
import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue'

Vue.config.productionTip = false
@@ -16,5 +17,6 @@ Vue.use(BootstrapVueIcons)
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app')

+ 6
- 2
src/utils/time.js View File

@@ -3,12 +3,16 @@ import momentDurationFormatSetup from 'moment-duration-format'

momentDurationFormatSetup(moment)

function extractLocale (locale) {
return locale.split("-")[0].toLowerCase()
}

const secondsToDdHhMmSs = (seconds, locale = 'en') => {
return moment.duration(seconds, 'seconds').locale(locale).format()
return moment.duration(seconds, 'seconds').locale(extractLocale(locale)).format()
}

const formatShort = (date, locale = 'en') => {
return moment(date).locale(locale).format('ddd D, HH:mm:ss')
return moment(date).locale(extractLocale(locale)).format('ddd D, HH:mm:ss')
}

const timeUtils = {


+ 19
- 1
src/views/About.vue View File

@@ -1,5 +1,23 @@
<template>
<div class="about">
<h1>This is an about page</h1>

<b-row align-h="center" class="text-center">
<b-col cols="8">
{{ $t('about.description') }}<br/><br/>
<a href="#" @click="openAboutPage">{{ $t('about.sources') }}</a>
</b-col>
</b-row>

</div>
</template>

<script>
export default {
methods: {
openAboutPage: function (event) {
event.preventDefault()
window.ipcRenderer.send('openAboutLink')
}
}
}
</script>

+ 42
- 1
src/views/Settings.vue View File

@@ -1,5 +1,46 @@
<template>
<div class="settings">
<h1>This is a settings page</h1>

<b-form @submit="saveSettings">
<b-row align-h="center">
<b-col cols="5">
<b-form-group
id="igLocale"
:label="$t('settings.form.locale')"
label-for="locale"
>
<b-form-select v-model="locale" :options="availableLocales" class="mb-2"></b-form-select>
</b-form-group>
</b-col>
</b-row>

<b-row align-h="center">
<b-col cols="4" align="center">
<b-button type="submit" variant="primary">{{ $t("settings.form.save") }}</b-button>
</b-col>
</b-row>
</b-form>

</div>
</template>

<script>
export default {
data: () => ({
locale: 'en-US',
availableLocales: [ { value: 'en-US', text: 'English'}, { value: 'fr-FR', text: 'Français' } ]
}),
mounted () {
this.locale = this.$root.$i18n.locale
console.log(this.locale)
},
methods: {
saveSettings (event) {
console.log("saving settings", this.locale)
event.preventDefault();
this.$root.$i18n.locale = this.locale
window.ipcRenderer.send('setPreference', {key: 'locale', value: this.locale})
}
}
}
</script>

+ 64
- 1
yarn.lock View File

@@ -1743,6 +1743,11 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==

atomically@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.3.2.tgz#721156e5c4f03e768ab54f3e6c9dc550d4690761"
integrity sha512-MAiqx5ir1nOoMeG2vLXJnj4oFROJYB1hMqa2aAo6GQVIkPdkIcrq9W9SR0OaRtvEowO7Y2bsXqKFuDMTO4iOAQ==

autoprefixer@^9.8.0:
version "9.8.5"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.5.tgz#2c225de229ddafe1d1424c02791d0c3e10ccccaa"
@@ -2685,6 +2690,22 @@ concat-stream@^1.5.0, concat-stream@^1.6.2:
readable-stream "^2.2.2"
typedarray "^0.0.6"

conf@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/conf/-/conf-7.1.1.tgz#327698d294a5c76fac17f970e47f87bcf1f0dc5b"
integrity sha512-njOu3so+7zcR1oQzXKP0mpPMKRf+riaaWmxBUhgP/c9k32PuDX/SQ+N6cO6ZylY6NOZGPwgzicGxdlRGXUOkSQ==
dependencies:
ajv "^6.12.2"
atomically "^1.3.1"
debounce-fn "^4.0.0"
dot-prop "^5.2.0"
env-paths "^2.2.0"
json-schema-typed "^7.0.3"
make-dir "^3.1.0"
onetime "^5.1.0"
pkg-up "^3.1.0"
semver "^7.3.2"

config-chain@^1.1.11:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@@ -3091,6 +3112,13 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=

debounce-fn@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
dependencies:
mimic-fn "^3.0.0"

debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -3503,6 +3531,14 @@ electron-publish@22.7.0:
lazy-val "^1.0.4"
mime "^2.4.5"

electron-store@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-6.0.0.tgz#92a5f8295a326f074281ae0d6a307454e6f68243"
integrity sha512-ujb0a/6gxMxb9vOQ2BjOehK9VCyq5OKvttekd9v/tohA9oBHnAdV+Vxu4eoRh+/F9ShPFhcvDZkMdqO5i+TXUw==
dependencies:
conf "^7.1.1"
type-fest "^0.16.0"

electron-to-chromium@^1.3.488:
version "1.3.502"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.502.tgz#6a55e993ef60a01fbdc2152ef5e47ee00c885c98"
@@ -5593,6 +5629,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==

json-schema-typed@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==

json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -5963,7 +6004,7 @@ make-dir@^2.0.0:
pify "^4.0.1"
semver "^5.6.0"

make-dir@^3.0.0, make-dir@^3.0.2:
make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -6122,6 +6163,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==

mimic-fn@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.0.0.tgz#76044cfa8818bbf6999c5c9acadf2d3649b14b4b"
integrity sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ==

mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@@ -7143,6 +7189,13 @@ pkg-dir@^4.1.0:
dependencies:
find-up "^4.0.0"

pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"

pnp-webpack-plugin@^1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@@ -9186,6 +9239,11 @@ type-fest@^0.13.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==

type-fest@^0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==

type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
@@ -9570,6 +9628,11 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==

vue-i18n@^8.18.2:
version "8.18.2"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.18.2.tgz#cd7c12f2e178e6faa23b0e3cfd2f7bac9305f8fc"
integrity sha512-0X5nBTCZAVjlwcrPaYJwNs3iipBBTv0AUHwQUOa8yP3XbQGWKbRHqBb3OhCYtum/IHDD21d/df5Xd2VgyxbxfA==

vue-loader@^15.9.2:
version "15.9.3"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.3.tgz#0de35d9e555d3ed53969516cac5ce25531299dda"


Loading…
Cancel
Save