Improve i18n handling (#5342)
This commit is contained in:
parent
2fdb2eb37f
commit
5ddf3c841f
34 changed files with 1552 additions and 3309 deletions
|
|
@ -3,7 +3,7 @@ import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import smoothscroll from "smoothscroll-polyfill";
|
import smoothscroll from "smoothscroll-polyfill";
|
||||||
import VueNumber from "vue-number-animation";
|
import VueNumber from "vue-number-animation";
|
||||||
import i18n from "./js/i18n";
|
import setupI18n from "./js/i18n";
|
||||||
import "./css/app.css";
|
import "./css/app.css";
|
||||||
import { watchThemeChanges } from "./js/theme";
|
import { watchThemeChanges } from "./js/theme";
|
||||||
|
|
||||||
|
|
@ -11,6 +11,6 @@ smoothscroll.polyfill();
|
||||||
watchThemeChanges();
|
watchThemeChanges();
|
||||||
|
|
||||||
export const setupVue3 = defineSetupVue3(({ app }) => {
|
export const setupVue3 = defineSetupVue3(({ app }) => {
|
||||||
app.use(i18n);
|
app.use(setupI18n());
|
||||||
app.use(VueNumber);
|
app.use(VueNumber);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package i18n
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed *.toml
|
|
||||||
var LocaleFS embed.FS
|
|
||||||
|
|
@ -15,7 +15,7 @@ dark = "Design: donker"
|
||||||
availableShort = "update"
|
availableShort = "update"
|
||||||
availableLong = "update beschikbaar"
|
availableLong = "update beschikbaar"
|
||||||
modalTitle = "Update beschikbaar"
|
modalTitle = "Update beschikbaar"
|
||||||
modalUpdateStarted = "Evcc zal herstarten na de update.."
|
modalUpdateStarted = "Evcc zal herstarten na de update."
|
||||||
modalInstalledVersion = "Huidige versie"
|
modalInstalledVersion = "Huidige versie"
|
||||||
modalNoReleaseNotes = "Geen release notes beschikbaar. Meer informatie over de nieuwe versie vind je hier:"
|
modalNoReleaseNotes = "Geen release notes beschikbaar. Meer informatie over de nieuwe versie vind je hier:"
|
||||||
modalCancel = "Annuleer"
|
modalCancel = "Annuleer"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ import axios from "axios";
|
||||||
|
|
||||||
const { protocol, hostname, port, pathname } = window.location;
|
const { protocol, hostname, port, pathname } = window.location;
|
||||||
|
|
||||||
|
const base = protocol + "//" + hostname + (port ? ":" + port : "") + pathname;
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: protocol + "//" + hostname + (port ? ":" + port : "") + pathname + "api/",
|
baseURL: base + "api/",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
|
|
@ -19,4 +21,12 @@ api.interceptors.response.use(
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
||||||
|
export const i18n = axios.create({
|
||||||
|
baseURL: base + "i18n/",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/toml",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import { createApp, h } from "vue";
|
||||||
import { createMetaManager, plugin as metaPlugin } from "vue-meta";
|
import { createMetaManager, plugin as metaPlugin } from "vue-meta";
|
||||||
import App from "./views/App.vue";
|
import App from "./views/App.vue";
|
||||||
import VueNumber from "vue-number-animation";
|
import VueNumber from "vue-number-animation";
|
||||||
import router from "./router";
|
import setupRouter from "./router";
|
||||||
import i18n from "./i18n";
|
import setupI18n from "./i18n";
|
||||||
import featureflags from "./featureflags";
|
import featureflags from "./featureflags";
|
||||||
import { watchThemeChanges } from "./theme";
|
import { watchThemeChanges } from "./theme";
|
||||||
|
|
||||||
|
|
@ -64,8 +64,9 @@ const app = createApp({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const i18n = setupI18n();
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
app.use(router);
|
app.use(setupRouter(i18n));
|
||||||
app.use(createMetaManager());
|
app.use(createMetaManager());
|
||||||
app.use(metaPlugin);
|
app.use(metaPlugin);
|
||||||
app.use(featureflags);
|
app.use(featureflags);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
|
import toml from "toml";
|
||||||
|
import { nextTick } from "vue";
|
||||||
import { createI18n } from "vue-i18n";
|
import { createI18n } from "vue-i18n";
|
||||||
import de from "../i18n/de.toml";
|
import en from "../../i18n/en.toml";
|
||||||
import en from "../i18n/en.toml";
|
import { i18n as i18nApi } from "./api";
|
||||||
import fr from "../i18n/fr.toml";
|
|
||||||
import it from "../i18n/it.toml";
|
|
||||||
import lt from "../i18n/lt.toml";
|
|
||||||
import nl from "../i18n/nl.toml";
|
|
||||||
import nb_no from "../i18n/nb_NO.toml";
|
|
||||||
import pl from "../i18n/pl.toml";
|
|
||||||
import pt from "../i18n/pt.toml";
|
|
||||||
|
|
||||||
const PREFERRED_LOCALE_KEY = "preferred_locale";
|
const PREFERRED_LOCALE_KEY = "preferred_locale";
|
||||||
|
|
||||||
|
|
@ -20,8 +15,43 @@ function getBrowserLocale() {
|
||||||
return navigatorLocale.trim().split(/-|_/)[0];
|
return navigatorLocale.trim().split(/-|_/)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createI18n({
|
function getLocale() {
|
||||||
locale: window.localStorage[PREFERRED_LOCALE_KEY] || getBrowserLocale(),
|
return window.localStorage[PREFERRED_LOCALE_KEY] || getBrowserLocale();
|
||||||
fallbackLocale: "en",
|
}
|
||||||
messages: { de, en, fr, it, lt, nb_no, nl, pl, pt },
|
|
||||||
});
|
export default function setupI18n() {
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: true,
|
||||||
|
silentFallbackWarn: true,
|
||||||
|
silentTranslationWarn: true,
|
||||||
|
locale: "en",
|
||||||
|
fallbackLocale: "en",
|
||||||
|
messages: { en },
|
||||||
|
});
|
||||||
|
setI18nLanguage(i18n, getLocale());
|
||||||
|
return i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setI18nLanguage(i18n, locale) {
|
||||||
|
i18n.global.locale = locale;
|
||||||
|
document.querySelector("html").setAttribute("lang", locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLocaleMessages(i18n, locale) {
|
||||||
|
try {
|
||||||
|
const response = await i18nApi.get(`${locale}.toml`, { params: { v: window.evcc?.version } });
|
||||||
|
const messages = toml.parse(response.data);
|
||||||
|
i18n.global.setLocaleMessage(locale, messages);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`unable to load translation for [${locale}]`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureCurrentLocaleMessages(i18n) {
|
||||||
|
const { locale } = i18n.global;
|
||||||
|
if (!i18n.global.availableLocales.includes(locale)) {
|
||||||
|
await loadLocaleMessages(i18n, locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,19 @@ import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
|
|
||||||
import Main from "./views/Main.vue";
|
import Main from "./views/Main.vue";
|
||||||
import ChargingSessions from "./views/ChargingSessions.vue";
|
import ChargingSessions from "./views/ChargingSessions.vue";
|
||||||
|
import { ensureCurrentLocaleMessages } from "./i18n";
|
||||||
|
|
||||||
export default createRouter({
|
export default function setupRouter(i18n) {
|
||||||
history: createWebHashHistory(),
|
const router = createRouter({
|
||||||
routes: [
|
history: createWebHashHistory(),
|
||||||
{ path: "/", component: Main, props: true },
|
routes: [
|
||||||
{ path: "/sessions", component: ChargingSessions, props: true },
|
{ path: "/", component: Main, props: true },
|
||||||
],
|
{ path: "/sessions", component: ChargingSessions, props: true },
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
router.beforeEach(async () => {
|
||||||
|
await ensureCurrentLocaleMessages(i18n);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
74
dist/assets/index.4e414929.js
vendored
Normal file
74
dist/assets/index.4e414929.js
vendored
Normal file
File diff suppressed because one or more lines are too long
70
dist/assets/index.841a487a.js
vendored
70
dist/assets/index.841a487a.js
vendored
File diff suppressed because one or more lines are too long
6
dist/index.html
vendored
6
dist/index.html
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
<meta name="theme-color" content="#020318" />
|
<meta name="theme-color" content="#020318" />
|
||||||
|
|
||||||
<title>evcc</title>
|
<title>evcc</title>
|
||||||
<script type="module" crossorigin src="./assets/index.841a487a.js"></script>
|
<script type="module" crossorigin src="./assets/index.4e414929.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.932fb253.css">
|
<link rel="stylesheet" href="./assets/index.14ecea29.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -114,6 +114,9 @@ require (
|
||||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.19.2 // indirect
|
github.com/glebarez/go-sqlite v1.19.2 // indirect
|
||||||
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
|
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
|
||||||
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
|
||||||
github.com/go-kit/log v0.2.1 // indirect
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -264,6 +264,12 @@ github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B3
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 h1:zga7zaRE8HCbWjcXMDlfvmQtH0/kMVLo7cQ48dy6kWg=
|
||||||
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1/go.mod h1:PumS+5d59wmAGsZo6IfRpVNaJUq+6xjC4Utt/k8GO6Q=
|
||||||
|
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2DMosGsXzlMe2E9qXgXCVkRLCoRX+5amxI=
|
||||||
|
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
|
||||||
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
|
||||||
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ dark = "Design: dark"
|
||||||
availableShort = "update"
|
availableShort = "update"
|
||||||
availableLong = "update available"
|
availableLong = "update available"
|
||||||
modalTitle = "Update available"
|
modalTitle = "Update available"
|
||||||
modalUpdateStarted = "Evcc will restart after the update.."
|
modalUpdateStarted = "Evcc will restart after the update."
|
||||||
modalInstalledVersion = "Currently installed version"
|
modalInstalledVersion = "Currently installed version"
|
||||||
modalNoReleaseNotes = "No release notes available. More information about the new version can be found here:"
|
modalNoReleaseNotes = "No release notes available. More information about the new version can be found here:"
|
||||||
modalCancel = "Cancel"
|
modalCancel = "Cancel"
|
||||||
220
i18n/nl.toml
Normal file
220
i18n/nl.toml
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
[header]
|
||||||
|
sessions = "Laadsessies"
|
||||||
|
docs = "Documentatie"
|
||||||
|
blog = "Blog"
|
||||||
|
github = "GitHub"
|
||||||
|
login = "Voertuig logins"
|
||||||
|
about = "Over evcc"
|
||||||
|
|
||||||
|
[header.theme]
|
||||||
|
auto = "Design: systeem"
|
||||||
|
light = "Design: licht"
|
||||||
|
dark = "Design: donker"
|
||||||
|
|
||||||
|
[footer.version]
|
||||||
|
availableShort = "update"
|
||||||
|
availableLong = "update beschikbaar"
|
||||||
|
modalTitle = "Update beschikbaar"
|
||||||
|
modalUpdateStarted = "Evcc zal herstarten na de update."
|
||||||
|
modalInstalledVersion = "Huidige versie"
|
||||||
|
modalNoReleaseNotes = "Geen release notes beschikbaar. Meer informatie over de nieuwe versie vind je hier:"
|
||||||
|
modalCancel = "Annuleer"
|
||||||
|
modalUpdate = "Update"
|
||||||
|
modalUpdateNow = "Nu updaten"
|
||||||
|
modalDownload = "Download"
|
||||||
|
modalUpdateStatusStart = "Update gestart:"
|
||||||
|
modalUpdateStatusFailed = "Update mislukt:"
|
||||||
|
|
||||||
|
[footer.savings]
|
||||||
|
tabTitle = "Mijn data"
|
||||||
|
footerShort = "{percent}% PV"
|
||||||
|
footerLong = "{percent}% zonne-energie"
|
||||||
|
modalTitle = "Energieoverzicht"
|
||||||
|
since = "sinds {since}"
|
||||||
|
percentTitle = "Zonne-energie"
|
||||||
|
percentSelf = "{self} kWh PV"
|
||||||
|
percentGrid = "{grid} kWh afgenomen"
|
||||||
|
priceTitle = "Energieprijs"
|
||||||
|
priceFeedIn = "{feedInPrice} injectie"
|
||||||
|
priceGrid = "{gridPrice} afname"
|
||||||
|
savingsTitle = "Besparing"
|
||||||
|
savingsComparedToGrid = "i.v.m. netafname"
|
||||||
|
savingsTotalEnergy = "{total} kWh geladen"
|
||||||
|
|
||||||
|
[footer.community]
|
||||||
|
tabTitle = "Live community"
|
||||||
|
power = "Laadvermogen"
|
||||||
|
powerSub1 = "{activeClients} van {totalClients} deelnemers"
|
||||||
|
powerSub2 = "zijn nu aan het laden"
|
||||||
|
greenShare = "Zonne-energie"
|
||||||
|
greenShareSub1 = "van de energie"
|
||||||
|
greenShareSub2 = "komt van PV & batterijopslag"
|
||||||
|
greenEnergy = "Zonne-energie"
|
||||||
|
greenEnergySub1 = "geladen met evcc"
|
||||||
|
greenEnergySub2 = "sinds oktober 2022"
|
||||||
|
|
||||||
|
[footer.telemetry]
|
||||||
|
optIn = "Ik wil mijn data ook bijdragen."
|
||||||
|
optInMoreDetails = "Meer info is {0} beschikbaar."
|
||||||
|
optInMoreDetailsLink = "hier"
|
||||||
|
optInSponsorship = "(Sponsoring nodig)"
|
||||||
|
|
||||||
|
[footer.sponsor]
|
||||||
|
titleSponsor = "Je bent een sponsor!"
|
||||||
|
titleNoSponsor = "Sponsor ons"
|
||||||
|
thanks = "Bedankt voor je hulp, {sponsor}! Je helpt ons bij de verdere ontwikkeling van evcc."
|
||||||
|
confetti = "Klaar voor confetti?"
|
||||||
|
supportUs = "Onze missie: Van laden op zonne-energie de standaard maken. Help ons en draag financieel bij aan evcc."
|
||||||
|
sticker = "… of evcc stickers?"
|
||||||
|
confettiPromise = "Er zullen stickers en digitale confetti zijn ;)"
|
||||||
|
becomeSponsor = "Word sponsor"
|
||||||
|
|
||||||
|
[notifications]
|
||||||
|
modalTitle = "Notificaties"
|
||||||
|
dismissAll = "Alles verwijderen"
|
||||||
|
|
||||||
|
[main]
|
||||||
|
vehicles = "Parking"
|
||||||
|
|
||||||
|
[main.energyflow]
|
||||||
|
noEnergy = "Geen meter data"
|
||||||
|
homePower = "Consumptie"
|
||||||
|
pvProduction = "Productie"
|
||||||
|
loadpoints = "Laadpunt | Laadpunt | {count} Laadpunten"
|
||||||
|
battery = "Batterij"
|
||||||
|
batteryCharge = "Batterij laden"
|
||||||
|
batteryDischarge = "Batterij ontladen"
|
||||||
|
gridImport = "Netafname"
|
||||||
|
selfConsumption = "Zelfverbruik"
|
||||||
|
pvExport = "Netinjectie"
|
||||||
|
|
||||||
|
[main.mode]
|
||||||
|
off = "Uit"
|
||||||
|
minpv = "Min+PV"
|
||||||
|
pv = "PV"
|
||||||
|
now = "Snel"
|
||||||
|
|
||||||
|
[main.loadpoint]
|
||||||
|
fallbackName = "Laadpunt"
|
||||||
|
remoteDisabledSoft = "{source}: adaptief PV-laden uitgeschakeld"
|
||||||
|
remoteDisabledHard = "{source}: uitgeschakeld"
|
||||||
|
power = "Snelheid"
|
||||||
|
charged = "Geladen"
|
||||||
|
duration = "Duur"
|
||||||
|
remaining = "Resterend"
|
||||||
|
|
||||||
|
[main.loadpointSettings]
|
||||||
|
title = 'Instellingen "{0}"'
|
||||||
|
vehicle = "Voertuig"
|
||||||
|
currents = "Opladen"
|
||||||
|
default = "standaard"
|
||||||
|
disclaimerHint = "Letop:"
|
||||||
|
disclaimerText = "Instellingen worden niet bijgehouden. Ze worden gereset bij het herstarten van de server."
|
||||||
|
|
||||||
|
[main.loadpointSettings.minSoC]
|
||||||
|
label = "Min batterijniveau"
|
||||||
|
description = 'Noodbereik. Voertuig wordt "snel" geladen tot {0}% in PV mode. Daarna wordt enkel de PV-overschot gebruikt.'
|
||||||
|
|
||||||
|
[main.loadpointSettings.phasesConfigured]
|
||||||
|
label = "Fasen"
|
||||||
|
phases_0 = "automatisch wisselend"
|
||||||
|
phases_1 = "1 fase"
|
||||||
|
phases_1_hint = "({min} - {max})"
|
||||||
|
phases_3 = "3 fasen"
|
||||||
|
phases_3_hint = "({min} - {max})"
|
||||||
|
|
||||||
|
[main.loadpointSettings.maxCurrent]
|
||||||
|
label = "Max. vermogen"
|
||||||
|
|
||||||
|
[main.loadpointSettings.minCurrent]
|
||||||
|
label = "Min. vermogen"
|
||||||
|
|
||||||
|
[main.vehicle]
|
||||||
|
fallbackName = "Voertuig"
|
||||||
|
vehicleSoC = "Batterijniveau"
|
||||||
|
targetSoC = "Limiet"
|
||||||
|
none = "Geen voertuig"
|
||||||
|
unknown = "Gast"
|
||||||
|
changeVehicle = "Wijzig voertuig"
|
||||||
|
detectionActive = "Voertuig detecteren ..."
|
||||||
|
|
||||||
|
[main.vehicleSoC]
|
||||||
|
disconnected = "niet verbonden"
|
||||||
|
charging = "laden"
|
||||||
|
ready = "klaar"
|
||||||
|
connected = "verbonden"
|
||||||
|
vehicleTarget = "Voertuiglimiet: {soc}%"
|
||||||
|
|
||||||
|
[main.vehicleStatus]
|
||||||
|
minCharge = "laden naar {soc}% minimum."
|
||||||
|
waitForVehicle = "Klaar. Wachten op voertuig."
|
||||||
|
vehicleTargetReached = "Voertuiglimiet {soc}% bereikt."
|
||||||
|
charging = "Laden."
|
||||||
|
targetChargePlanned = "Doel gepland. Starten op {time}."
|
||||||
|
targetChargeWaitForVehicle = "Doel gepland. Wachten op voertuig."
|
||||||
|
targetChargeActive = "Doelladen actief."
|
||||||
|
connected = "Verbonden."
|
||||||
|
pvDisable = "Onvoldoende PV overschot beschikbaar. Pauzeren over {remaining}."
|
||||||
|
pvEnable = "Voldoende PV overschot beschikbaar. Starten over {remaining}."
|
||||||
|
scale1p = "Wisselen naar 1 fase over {remaining}."
|
||||||
|
scale3p = "Wisselen naar 3 fasen over {remaining}."
|
||||||
|
disconnected = "Niet verbonden."
|
||||||
|
unknown = ""
|
||||||
|
|
||||||
|
[main.provider]
|
||||||
|
login = "aanmelden"
|
||||||
|
logout = "afmelden"
|
||||||
|
|
||||||
|
[main.targetCharge]
|
||||||
|
title = "Doel"
|
||||||
|
inactiveLabel = "Ingestelde tijd"
|
||||||
|
activeLabel = "{time}"
|
||||||
|
modalTitle = "Stel tijd in"
|
||||||
|
setTargetTime = "geen"
|
||||||
|
description = "Tegen wanneer moet het voertuig geladen zijn naar {targetSoC}%?"
|
||||||
|
today = "Vandaag"
|
||||||
|
tomorrow = "Morgen"
|
||||||
|
targetIsInThePast = "De ingestelde tijd ligt in het verleden"
|
||||||
|
remove = "Verwijder"
|
||||||
|
activate = "Activeer"
|
||||||
|
experimentalLabel = "Experimenteel"
|
||||||
|
experimentalText = "Deze feature werkt nog niet perfect. Rapporteer ongewenst gedrag op"
|
||||||
|
|
||||||
|
[main.targetEnergy]
|
||||||
|
label = "Limiet"
|
||||||
|
noLimit = "geen"
|
||||||
|
|
||||||
|
[startupError]
|
||||||
|
title = "Start Error"
|
||||||
|
description = "Check je evcc configuratie. Helpt de foutboodschap je niet, bekijk dan de {0}."
|
||||||
|
discussions = "GitHub Discussions"
|
||||||
|
hint = "Je kan deze boodschap ook krijgen bij een kapot toestel (inverter, meter, ...). Check je network connectie."
|
||||||
|
configuration = "Config"
|
||||||
|
configFile = "Gebruikt configuratiebestand:"
|
||||||
|
lineError = "Fout gedetecteerd op {0}."
|
||||||
|
lineErrorLink = "lijn {0}"
|
||||||
|
fixAndRestart = "Verhelp het probleem en herstart de server."
|
||||||
|
restartButton = "Herstart"
|
||||||
|
|
||||||
|
[sessions]
|
||||||
|
title = "Laadsessies"
|
||||||
|
downloadCsv = "Download CSV"
|
||||||
|
loadpoint = "Laadpunt"
|
||||||
|
vehicle = "Voertuig"
|
||||||
|
energy = "Geladen"
|
||||||
|
date = "Periode"
|
||||||
|
|
||||||
|
[sessions.csv]
|
||||||
|
loadpoint = "Laadpunt"
|
||||||
|
vehicle = "Voertuig"
|
||||||
|
odometer = "kilometerstand (km)"
|
||||||
|
identifier = "ID"
|
||||||
|
chargedenergy = "Energie (kWh)"
|
||||||
|
meterstart = "Meter Start (kWh)"
|
||||||
|
meterstop = "Meter Stop (kWh)"
|
||||||
|
created = "Gestart"
|
||||||
|
finished = "Beëindigd"
|
||||||
|
|
||||||
|
[offline]
|
||||||
|
message = "Geen verbinding met de server."
|
||||||
|
reload = "Opnieuw laden?"
|
||||||
24
main.go
24
main.go
|
|
@ -9,23 +9,35 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/evcc-io/evcc/cmd"
|
"github.com/evcc-io/evcc/cmd"
|
||||||
"github.com/evcc-io/evcc/server"
|
"github.com/evcc-io/evcc/server/assets"
|
||||||
_ "github.com/evcc-io/evcc/util/goversion" // require minimum go version
|
_ "github.com/evcc-io/evcc/util/goversion" // require minimum go version
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dist
|
var (
|
||||||
var assets embed.FS
|
//go:embed dist
|
||||||
|
web embed.FS
|
||||||
|
|
||||||
|
//go:embed i18n/*.toml
|
||||||
|
i18n embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
// init loads embedded assets unless live assets are already loaded
|
// init loads embedded assets unless live assets are already loaded
|
||||||
func init() {
|
func init() {
|
||||||
|
// TODO remove this once Go 1.20 is out with it.
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
if server.Assets == nil {
|
if !assets.Live() {
|
||||||
fsys, err := fs.Sub(assets, "dist")
|
var err error
|
||||||
|
|
||||||
|
assets.Web, err = fs.Sub(web, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.I18n, err = fs.Sub(i18n, "i18n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
server.Assets = fsys
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4256
package-lock.json
generated
4256
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
|
@ -18,9 +18,11 @@
|
||||||
"bootstrap": "^5.2.2",
|
"bootstrap": "^5.2.2",
|
||||||
"canvas-confetti": "^1.4.0",
|
"canvas-confetti": "^1.4.0",
|
||||||
"smoothscroll-polyfill": "^0.4.4",
|
"smoothscroll-polyfill": "^0.4.4",
|
||||||
|
"toml": "^3.0.0",
|
||||||
|
"vite-plugin-toml": "^0.5.0",
|
||||||
"vue": "^3.2.29",
|
"vue": "^3.2.29",
|
||||||
"vue-hot-reload-api": "^2.3.4",
|
"vue-hot-reload-api": "^2.3.4",
|
||||||
"vue-i18n": "^9.1.9",
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-meta": "^3.0.0-alpha.2",
|
"vue-meta": "^3.0.0-alpha.2",
|
||||||
"vue-number-animation": "^1.1.2",
|
"vue-number-animation": "^1.1.2",
|
||||||
"vue-router": "^4.0.12"
|
"vue-router": "^4.0.12"
|
||||||
|
|
@ -29,7 +31,6 @@
|
||||||
"@babel/core": "^7.17.2",
|
"@babel/core": "^7.17.2",
|
||||||
"@histoire/plugin-vue": "^0.11.6",
|
"@histoire/plugin-vue": "^0.11.6",
|
||||||
"@vue/compiler-sfc": "^3.2.30",
|
"@vue/compiler-sfc": "^3.2.30",
|
||||||
"babel-loader": "^8.2.3",
|
|
||||||
"eslint": "^8.27.0",
|
"eslint": "^8.27.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
|
|
@ -38,14 +39,11 @@
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
"histoire": "^0.11.6",
|
"histoire": "^0.11.6",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
|
||||||
"postcss-custom-media": "^8.0.2",
|
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"prettier-plugin-toml": "^0.3.1",
|
"prettier-plugin-toml": "^0.3.1",
|
||||||
|
"rollup-plugin-analyzer": "^4.0.0",
|
||||||
"vite": "^3.0.4",
|
"vite": "^3.0.4",
|
||||||
"vite-plugin-toml": "^0.4.0",
|
"vue-eslint-parser": "^9.1.0"
|
||||||
"vue-eslint-parser": "^9.1.0",
|
|
||||||
"vue-loader": "^17.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">=8.0.0",
|
"npm": ">=8.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
const postcssCustomMedia = require("postcss-custom-media");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
plugins: [postcssCustomMedia()],
|
|
||||||
};
|
|
||||||
16
server/assets/assets.go
Normal file
16
server/assets/assets.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package assets
|
||||||
|
|
||||||
|
import "io/fs"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Web is the embedded dist file system
|
||||||
|
Web fs.FS
|
||||||
|
|
||||||
|
// I18n is the embedded i18n file system
|
||||||
|
I18n fs.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
// Live indicates assets are passed-through from filesystem
|
||||||
|
func Live() bool {
|
||||||
|
return Web != nil
|
||||||
|
}
|
||||||
12
server/assets/assets_live.go
Normal file
12
server/assets/assets_live.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build !release
|
||||||
|
|
||||||
|
package assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Web = os.DirFS("dist")
|
||||||
|
I18n = os.DirFS("i18n")
|
||||||
|
}
|
||||||
|
|
@ -2,20 +2,18 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/evcc-io/evcc/core/site"
|
"github.com/evcc-io/evcc/core/site"
|
||||||
|
"github.com/evcc-io/evcc/server/assets"
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
"github.com/evcc-io/evcc/util/telemetry"
|
"github.com/evcc-io/evcc/util/telemetry"
|
||||||
|
"github.com/go-http-utils/etag"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assets is the embedded assets file system
|
|
||||||
var Assets fs.FS
|
|
||||||
|
|
||||||
type route struct {
|
type route struct {
|
||||||
Methods []string
|
Methods []string
|
||||||
Pattern string
|
Pattern string
|
||||||
|
|
@ -53,11 +51,15 @@ func NewHTTPd(addr string, hub *SocketHub) *HTTPd {
|
||||||
// static - individual handlers per root and folders
|
// static - individual handlers per root and folders
|
||||||
static := router.PathPrefix("/").Subrouter()
|
static := router.PathPrefix("/").Subrouter()
|
||||||
static.Use(handlers.CompressHandler)
|
static.Use(handlers.CompressHandler)
|
||||||
|
static.Use(handlers.CompressHandler, func(h http.Handler) http.Handler {
|
||||||
|
return etag.Handler(h, false)
|
||||||
|
})
|
||||||
|
|
||||||
static.HandleFunc("/", indexHandler())
|
static.HandleFunc("/", indexHandler())
|
||||||
for _, dir := range []string{"assets", "meta"} {
|
for _, dir := range []string{"assets", "meta"} {
|
||||||
static.PathPrefix("/" + dir).Handler(http.FileServer(http.FS(Assets)))
|
static.PathPrefix("/" + dir).Handler(http.FileServer(http.FS(assets.Web)))
|
||||||
}
|
}
|
||||||
|
static.PathPrefix("/i18n").Handler(http.StripPrefix("/i18n", http.FileServer(http.FS(assets.I18n))))
|
||||||
|
|
||||||
srv := &HTTPd{
|
srv := &HTTPd{
|
||||||
Server: &http.Server{
|
Server: &http.Server{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/evcc-io/evcc/core/db"
|
"github.com/evcc-io/evcc/core/db"
|
||||||
"github.com/evcc-io/evcc/core/loadpoint"
|
"github.com/evcc-io/evcc/core/loadpoint"
|
||||||
"github.com/evcc-io/evcc/core/site"
|
"github.com/evcc-io/evcc/core/site"
|
||||||
|
"github.com/evcc-io/evcc/server/assets"
|
||||||
dbserver "github.com/evcc-io/evcc/server/db"
|
dbserver "github.com/evcc-io/evcc/server/db"
|
||||||
"github.com/evcc-io/evcc/util"
|
"github.com/evcc-io/evcc/util"
|
||||||
"github.com/evcc-io/evcc/util/locale"
|
"github.com/evcc-io/evcc/util/locale"
|
||||||
|
|
@ -28,7 +29,7 @@ func indexHandler() http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
||||||
|
|
||||||
indexTemplate, err := fs.ReadFile(Assets, "index.html")
|
indexTemplate, err := fs.ReadFile(assets.Web, "index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FATAL.Print("httpd: failed to load embedded template:", err.Error())
|
log.FATAL.Print("httpd: failed to load embedded template:", err.Error())
|
||||||
log.FATAL.Print("Make sure templates are included using the `release` build tag or use `make build`")
|
log.FATAL.Print("Make sure templates are included using the `release` build tag or use `make build`")
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
//go:build !release
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Assets = os.DirFS("dist")
|
|
||||||
}
|
|
||||||
|
|
@ -2,10 +2,11 @@ package locale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/cloudfoundry/jibber_jabber"
|
"github.com/cloudfoundry/jibber_jabber"
|
||||||
assets "github.com/evcc-io/evcc/assets/i18n"
|
"github.com/evcc-io/evcc/server/assets"
|
||||||
"github.com/evcc-io/evcc/util/locale/internal"
|
"github.com/evcc-io/evcc/util/locale/internal"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
@ -25,13 +26,13 @@ func Init() error {
|
||||||
Bundle = i18n.NewBundle(language.English)
|
Bundle = i18n.NewBundle(language.English)
|
||||||
Bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
Bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||||
|
|
||||||
dir, err := assets.LocaleFS.ReadDir(".")
|
dir, err := fs.ReadDir(assets.I18n, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range dir {
|
for _, d := range dir {
|
||||||
if _, err := Bundle.LoadMessageFileFS(assets.LocaleFS, d.Name()); err != nil {
|
if _, err := Bundle.LoadMessageFileFS(assets.I18n, d.Name()); err != nil {
|
||||||
return fmt.Errorf("loading locales failed: %w", err)
|
return fmt.Errorf("loading locales failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import vuePlugin from "@vitejs/plugin-vue";
|
import vuePlugin from "@vitejs/plugin-vue";
|
||||||
import { ViteToml } from "vite-plugin-toml";
|
import { ViteToml } from "vite-plugin-toml";
|
||||||
|
//import analyze from "rollup-plugin-analyzer";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
root: "./assets",
|
root: "./assets",
|
||||||
|
|
@ -20,6 +21,7 @@ export default defineConfig({
|
||||||
port: 7071,
|
port: 7071,
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": "http://localhost:7070",
|
"/api": "http://localhost:7070",
|
||||||
|
"/i18n": "http://localhost:7070",
|
||||||
"/ws": { target: "ws://localhost:7070", ws: true },
|
"/ws": { target: "ws://localhost:7070", ws: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -32,5 +34,6 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
ViteToml(),
|
ViteToml(),
|
||||||
|
//analyze(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue