mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-23 00:33:18 +01:00
Add support of the Accept_language header for UI localization
This commit is contained in:
parent
fbe91cc90e
commit
9f574feada
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Services\SettingService;
|
use App\Services\SettingService;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
class SinglePageController extends Controller
|
class SinglePageController extends Controller
|
||||||
{
|
{
|
||||||
@ -31,7 +32,8 @@ public function index()
|
|||||||
{
|
{
|
||||||
return view('landing')->with([
|
return view('landing')->with([
|
||||||
'appSettings' => $this->settingService->all()->toJson(),
|
'appSettings' => $this->settingService->all()->toJson(),
|
||||||
'lang' => $this->settingService->get('lang')
|
'lang' => App::currentLocale(),
|
||||||
|
'locales' => collect(config("2fauth.locales"))->toJson(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
use Facades\App\Services\SettingService;
|
use Facades\App\Services\SettingService;
|
||||||
|
|
||||||
class SetLanguage
|
class SetLanguage
|
||||||
@ -16,7 +17,25 @@ class SetLanguage
|
|||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
\App::setLocale(SettingService::get('lang', 'en'));
|
// 3 possible cases here:
|
||||||
|
// - The user has choosen a specific language among those available in the Setting view of 2FAuth
|
||||||
|
// - The client send an accept-language header
|
||||||
|
// - No language is passed from the client
|
||||||
|
//
|
||||||
|
// We prioritize the user defined one, then the request header one, and finally the fallback one.
|
||||||
|
// FI: SettingService::get() always returns a fallback value
|
||||||
|
$lang = SettingService::get('lang');
|
||||||
|
|
||||||
|
if($lang === 'browser') {
|
||||||
|
if ($request->hasHeader("Accept-Language")) {
|
||||||
|
// We only keep the primary language passed via the header.
|
||||||
|
$lang = head(explode(',', $request->header("Accept-Language")));
|
||||||
|
}
|
||||||
|
else $lang = config('app.fallback_locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the language is not available (or partial), strings will be translated using the fallback language.
|
||||||
|
App::setLocale($lang);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,29 @@
|
|||||||
use Throwable;
|
use Throwable;
|
||||||
use Exception;
|
use Exception;
|
||||||
use App\Models\Option;
|
use App\Models\Option;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
use App\Exceptions\DbEncryptionException;
|
use App\Exceptions\DbEncryptionException;
|
||||||
|
|
||||||
class SettingService
|
class SettingService
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given setting has been customized by the user
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isUserDefined($key) : bool
|
||||||
|
{
|
||||||
|
return DB::table('options')->where('key', $key)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a setting
|
* Get a setting
|
||||||
*
|
*
|
||||||
@ -40,9 +55,15 @@ public function all() : Collection
|
|||||||
$userOptions->transform(function ($item, $key) {
|
$userOptions->transform(function ($item, $key) {
|
||||||
return $this->restoreType($item);
|
return $this->restoreType($item);
|
||||||
});
|
});
|
||||||
$userOptions = collect(config('2fauth.options'))->merge($userOptions);
|
|
||||||
|
|
||||||
return $userOptions;
|
// Merge 2fauth/app config values as fallback values
|
||||||
|
$settings = collect(config('2fauth.options'))->merge($userOptions);
|
||||||
|
|
||||||
|
if(!Arr::has($settings, 'lang')) {
|
||||||
|
$settings['lang'] = 'browser';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,19 @@
|
|||||||
'isDemoApp' => env('IS_DEMO_APP', false),
|
'isDemoApp' => env('IS_DEMO_APP', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 2FAuth available translations
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'locales' => [
|
||||||
|
'en',
|
||||||
|
'fr',
|
||||||
|
'de'
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application fallback for user options
|
| Application fallback for user options
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
|
||||||
<!-- Language -->
|
<!-- Language -->
|
||||||
<form-select v-on:lang="saveSetting('lang', $event)" :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
|
<form-select v-on:lang="saveSetting('lang', $event)" :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
|
||||||
|
<div class="field help">{{ $t('settings.forms.some_translation_are_missing') }}<a class="ml-2" href="https://crowdin.com/project/2fauth">{{ $t('settings.forms.help_translate_2fauth') }}</a></div>
|
||||||
<!-- display mode -->
|
<!-- display mode -->
|
||||||
<form-toggle v-on:displayMode="saveSetting('displayMode', $event)" :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
|
<form-toggle v-on:displayMode="saveSetting('displayMode', $event)" :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
|
||||||
<!-- show icon -->
|
<!-- show icon -->
|
||||||
@ -74,7 +75,7 @@
|
|||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
form: new Form({
|
form: new Form({
|
||||||
lang: '',
|
lang: 'browser',
|
||||||
showOtpAsDot: null,
|
showOtpAsDot: null,
|
||||||
closeOtpOnCopy: null,
|
closeOtpOnCopy: null,
|
||||||
useBasicQrcodeReader: null,
|
useBasicQrcodeReader: null,
|
||||||
@ -87,11 +88,6 @@
|
|||||||
defaultCaptureMode: '',
|
defaultCaptureMode: '',
|
||||||
rememberActiveGroup: true,
|
rememberActiveGroup: true,
|
||||||
}),
|
}),
|
||||||
langs: [
|
|
||||||
{ text: this.$t('languages.en'), value: 'en' },
|
|
||||||
{ text: this.$t('languages.fr'), value: 'fr' },
|
|
||||||
{ text: this.$t('languages.de'), value: 'de' },
|
|
||||||
],
|
|
||||||
layouts: [
|
layouts: [
|
||||||
{ text: this.$t('settings.forms.grid'), value: 'grid', icon: 'th' },
|
{ text: this.$t('settings.forms.grid'), value: 'grid', icon: 'th' },
|
||||||
{ text: this.$t('settings.forms.list'), value: 'list', icon: 'list' },
|
{ text: this.$t('settings.forms.list'), value: 'list', icon: 'list' },
|
||||||
@ -119,11 +115,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed : {
|
||||||
|
langs: function() {
|
||||||
|
let locales = [{
|
||||||
|
text: this.$t('languages.browser_preference') + ' (' + this.$root.$i18n.locale + ')',
|
||||||
|
value: 'browser'
|
||||||
|
}];
|
||||||
|
|
||||||
|
for (const locale of window.appLocales) {
|
||||||
|
locales.push({
|
||||||
|
text: this.$t('languages.' + locale),
|
||||||
|
value: locale
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return locales
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const { data } = await this.form.get('/api/v1/settings')
|
const { data } = await this.form.get('/api/v1/settings')
|
||||||
|
|
||||||
this.form.fillWithKeyValueObject(data)
|
this.form.fillWithKeyValueObject(data)
|
||||||
this.form.lang = this.$root.$i18n.locale
|
let lang = data.filter(x => x.key === 'lang')
|
||||||
|
|
||||||
|
if (lang.value == 'browser') {
|
||||||
|
if(window.appLocales.includes(lang.value)) {
|
||||||
|
this.form.lang = lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this.$root.$i18n.locale
|
||||||
|
|
||||||
this.form.setOriginal()
|
this.form.setOriginal()
|
||||||
this.fetchGroups()
|
this.fetchGroups()
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'browser_preference' => 'Browser preference',
|
||||||
'en' => 'English',
|
'en' => 'English',
|
||||||
'fr' => 'French',
|
'fr' => 'French',
|
||||||
'de' => 'German',
|
'de' => 'German',
|
||||||
|
@ -39,9 +39,11 @@
|
|||||||
'edit_settings' => 'Edit settings',
|
'edit_settings' => 'Edit settings',
|
||||||
'setting_saved' => 'Settings saved',
|
'setting_saved' => 'Settings saved',
|
||||||
'new_token' => 'New token',
|
'new_token' => 'New token',
|
||||||
|
'some_translation_are_missing' => 'Some translations are missing using the browser preferred language?',
|
||||||
|
'help_translate_2fauth' => 'Help translate 2FAuth',
|
||||||
'language' => [
|
'language' => [
|
||||||
'label' => 'Language',
|
'label' => 'Language',
|
||||||
'help' => 'Change the language used to translate the app interface.'
|
'help' => 'Language used to translate the 2FAuth user interface. Named languages are complete, set the one of your choice to override your browser preference.'
|
||||||
],
|
],
|
||||||
'show_otp_as_dot' => [
|
'show_otp_as_dot' => [
|
||||||
'label' => 'Show generated one-time passwords as dot',
|
'label' => 'Show generated one-time passwords as dot',
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var appSettings = {!! $appSettings !!};
|
var appSettings = {!! $appSettings !!};
|
||||||
var appVersion = '{{ config("app.version") }}';
|
var appVersion = '{{ config("app.version") }}';
|
||||||
|
var appLocales = {!! $locales !!};
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ mix('js/manifest.js') }}"></script>
|
<script src="{{ mix('js/manifest.js') }}"></script>
|
||||||
<script src="{{ mix('js/vendor.js') }}"></script>
|
<script src="{{ mix('js/vendor.js') }}"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user