Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F118351972
D2395.1775774195.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None
D2395.1775774195.diff
View Options
diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -6,6 +6,7 @@
APP_PUBLIC_URL=
APP_DOMAIN=kolabnow.com
APP_THEME=default
+APP_LOCALE=en
ASSET_URL=http://127.0.0.1:8000
diff --git a/src/app/Http/Kernel.php b/src/app/Http/Kernel.php
--- a/src/app/Http/Kernel.php
+++ b/src/app/Http/Kernel.php
@@ -21,6 +21,7 @@
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\DevelConfig::class,
+ \App\Http\Middleware\Locale::class,
// FIXME: CORS handling added here, I didn't find a nice way
// to add this only to the API routes
// \App\Http\Middleware\Cors::class,
diff --git a/src/app/Http/Middleware/Locale.php b/src/app/Http/Middleware/Locale.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Middleware/Locale.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class Locale
+{
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ $langDir = resource_path('lang');
+ $preferences = array_map(
+ function ($lang) {
+ return preg_replace('/[^a-z].*$/', '', strtolower($lang));
+ },
+ $request->getLanguages()
+ );
+
+ $default = config('app.locale');
+ $lang = null;
+
+ foreach ($preferences as $pref) {
+ if (!empty($pref) && ($pref == $default || file_exists("$langDir/$pref"))) {
+ $lang = $pref;
+ break;
+ }
+ }
+
+ if ($lang != $default) {
+ app()->setLocale($lang);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -98,7 +98,7 @@
|
*/
- 'locale' => 'en',
+ 'locale' => env('APP_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
diff --git a/src/package.json b/src/package.json
--- a/src/package.json
+++ b/src/package.json
@@ -33,6 +33,7 @@
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0",
"vue": "^2.6.12",
+ "vue-i18n": "^8.24.1",
"vue-router": "^3.4.9",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.6.0"
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -10,6 +10,7 @@
import MenuComponent from '../vue/Widgets/Menu'
import SupportForm from '../vue/Widgets/SupportForm'
import store from './store'
+import { loadLanguageAsync, i18n } from './locale'
const loader = '<div class="app-loader"><div class="spinner-border" role="status"><span class="sr-only">Loading</span></div></div>'
@@ -53,7 +54,7 @@
loadingRoute = to.name
}
- next()
+ loadLanguageAsync().then(() => next())
})
window.router.afterEach((to, from) => {
@@ -74,6 +75,7 @@
AppComponent,
MenuComponent,
},
+ i18n,
store,
router: window.router,
data() {
diff --git a/src/resources/js/locale.js b/src/resources/js/locale.js
new file mode 100644
--- /dev/null
+++ b/src/resources/js/locale.js
@@ -0,0 +1,56 @@
+import Vue from 'vue'
+import VueI18n from 'vue-i18n'
+import messages from '../lang-js/en'
+
+Vue.use(VueI18n)
+
+export const i18n = new VueI18n({
+ locale: 'en',
+ fallbackLocale: 'en',
+ messages: { en: messages },
+ silentFallbackWarn: true
+})
+
+let currentLanguage
+
+const loadedLanguages = [ 'en' ] // our default language that is preloaded
+
+const setI18nLanguage = (lang) => {
+ i18n.locale = lang
+ window.axios.defaults.headers.common['Accept-Language'] = lang
+ document.querySelector('html').setAttribute('lang', lang)
+
+ return lang
+}
+
+export const getLang = () => {
+ // TODO: On init get the current user language from the browser?
+ if (!currentLanguage) {
+ currentLanguage = document.querySelector('html').getAttribute('lang') || 'en'
+ }
+
+ return currentLanguage
+}
+
+export const setLang = lang => {
+ // TODO: Save the selected language in the browser?
+ currentLanguage = lang
+ loadLanguageAsync()
+}
+
+export function loadLanguageAsync() {
+ const lang = getLang()
+
+ // If the language was already loaded
+ if (loadedLanguages.includes(lang)) {
+ return Promise.resolve(setI18nLanguage(lang))
+ }
+
+ // If the language hasn't been loaded yet
+ return import(/* webpackChunkName: "locale/[request]" */ `../lang-js/${lang}`)
+ .then(messages => {
+ i18n.setLocaleMessage(lang, messages.default)
+ loadedLanguages.push(lang)
+ return setI18nLanguage(lang)
+ })
+}
diff --git a/src/resources/lang-js/de.js b/src/resources/lang-js/de.js
new file mode 100644
--- /dev/null
+++ b/src/resources/lang-js/de.js
@@ -0,0 +1,18 @@
+export default {
+ buttons: {
+ cancel: "Stornieren",
+ save: "Speichern"
+ },
+ menu: {
+ cockpit: "Cockpit",
+ login: "Einloggen",
+ logout: "Ausloggen",
+ signup: "Signup",
+ toggle: "Navigation umschalten"
+ },
+ login: {
+ forgot_password: "Passwort vergessen?",
+ sign_in: "Anmelden",
+ webmail: "Webmail"
+ }
+}
diff --git a/src/resources/lang-js/en.js b/src/resources/lang-js/en.js
new file mode 100644
--- /dev/null
+++ b/src/resources/lang-js/en.js
@@ -0,0 +1,18 @@
+export default {
+ buttons: {
+ cancel: "Cancel",
+ save: "Save"
+ },
+ menu: {
+ cockpit: "Cockpit",
+ login: "Login",
+ logout: "Logout",
+ signup: "Signup",
+ toggle: "Toggle navigation"
+ },
+ login: {
+ forgot_password: "Forgot password?",
+ sign_in: "Sign in",
+ webmail: "Webmail"
+ }
+}
diff --git a/src/resources/themes/menu.scss b/src/resources/themes/menu.scss
--- a/src/resources/themes/menu.scss
+++ b/src/resources/themes/menu.scss
@@ -56,6 +56,17 @@
}
}
+#language-selector {
+ width: auto;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border: 0;
+ background-color: transparent;
+ padding-right: 1.4rem;
+ margin: 0.25em;
+}
+
@include media-breakpoint-up(lg) {
#header-menu {
a.menulogin {
diff --git a/src/resources/vue/Login.vue b/src/resources/vue/Login.vue
--- a/src/resources/vue/Login.vue
+++ b/src/resources/vue/Login.vue
@@ -35,7 +35,7 @@
</div>
<div class="text-center">
<button class="btn btn-primary" type="submit">
- <svg-icon icon="sign-in-alt"></svg-icon> Sign in
+ <svg-icon icon="sign-in-alt"></svg-icon> {{ $t('login.sign_in') }}
</button>
</div>
</form>
@@ -43,8 +43,8 @@
</div>
</div>
<div id="logon-form-footer" class="mt-1">
- <router-link v-if="!$root.isAdmin && $root.hasRoute('password-reset')" :to="{ name: 'password-reset' }" id="forgot-password">Forgot password?</router-link>
- <a v-if="webmailURL && !$root.isAdmin" :href="webmailURL" id="webmail">Webmail</a>
+ <router-link v-if="!$root.isAdmin && $root.hasRoute('password-reset')" :to="{ name: 'password-reset' }" id="forgot-password">{{ $t('login.forgot_password') }}</router-link>
+ <a v-if="webmailURL && !$root.isAdmin" :href="webmailURL" id="webmail">{{ $t('login.webmail') }}</a>
</div>
</div>
</template>
diff --git a/src/resources/vue/Widgets/Menu.vue b/src/resources/vue/Widgets/Menu.vue
--- a/src/resources/vue/Widgets/Menu.vue
+++ b/src/resources/vue/Widgets/Menu.vue
@@ -4,7 +4,7 @@
<router-link class="navbar-brand" to="/" v-html="$root.logo(mode)"></router-link>
<button v-if="mode == 'header'" class="navbar-toggler" type="button"
data-toggle="collapse" :data-target="'#' + mode + '-menu-navbar'"
- aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"
+ aria-controls="navbar" aria-expanded="false" :aria-label="$t('menu.toggle')"
>
<span class="navbar-toggler-icon"></span>
</button>
@@ -22,16 +22,16 @@
</router-link>
</li>
<li class="nav-item" v-if="!loggedIn && !$root.isAdmin">
- <router-link class="nav-link link-signup" active-class="active" :to="{name: 'signup'}">Signup</router-link>
+ <router-link class="nav-link link-signup" active-class="active" :to="{name: 'signup'}">{{ $t('menu.signup') }}</router-link>
</li>
<li class="nav-item" v-if="loggedIn">
- <router-link class="nav-link link-dashboard" active-class="active" :to="{name: 'dashboard'}">Cockpit</router-link>
+ <router-link class="nav-link link-dashboard" active-class="active" :to="{name: 'dashboard'}">{{ $t('menu.cockpit') }}</router-link>
</li>
<li class="nav-item" v-if="loggedIn">
- <router-link class="nav-link menulogin link-logout" active-class="active" :to="{name: 'logout'}">Logout</router-link>
+ <router-link class="nav-link menulogin link-logout" active-class="active" :to="{name: 'logout'}">{{ $t('menu.logout') }}</router-link>
</li>
<li class="nav-item" v-if="!loggedIn">
- <router-link class="nav-link menulogin link-login" :to="{name: 'login'}">Login</router-link>
+ <router-link class="nav-link menulogin link-login" :to="{name: 'login'}">{{ $t('menu.login') }}</router-link>
</li>
</ul>
<div v-if="mode == 'footer'" class="footer">
@@ -40,10 +40,16 @@
</div>
</div>
</div>
+ <select v-if="mode == 'header'" id="language-selector" class="custom-select custom-select-sm" @change="setLang()">
+ <option value="en" :selected="getLang() == 'en'">English</option>
+ <option value="de" :selected="getLang() == 'de'">German</option>
+ </select>
</nav>
</template>
<script>
+ import { setLang, getLang } from '../../js/locale'
+
export default {
props: {
mode: { type: String, default: 'header' },
@@ -93,6 +99,12 @@
})
return menu
+ },
+ getLang() {
+ return getLang()
+ },
+ setLang() {
+ setLang($('#language-selector').val())
}
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 9, 10:36 PM (14 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18853303
Default Alt Text
D2395.1775774195.diff (10 KB)
Attached To
Mode
D2395: Localization with vue-i18n
Attached
Detach File
Event Timeline