mirror of https://github.com/nextcloud/server
Remake locale saving with Vue
Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
parent
b9c002df0a
commit
f922b2fd70
|
@ -105,11 +105,6 @@ input#openid, input#webdav {
|
|||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr 2fr;
|
||||
}
|
||||
.profile-settings-container #locale h3 {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.personal-show-container {
|
||||
width: 100%;
|
||||
|
@ -125,7 +120,7 @@ input#openid, input#webdav {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
select#timezone, select#languageinput, select#localeinput {
|
||||
select#timezone {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -47,14 +47,6 @@ input {
|
|||
display: inline-grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr 2fr;
|
||||
|
||||
#locale {
|
||||
h3 {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-show-container {
|
||||
|
@ -78,9 +70,7 @@ input {
|
|||
}
|
||||
|
||||
select {
|
||||
&#timezone,
|
||||
&#languageinput,
|
||||
&#localeinput {
|
||||
&#timezone {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,62 +108,4 @@ window.addEventListener('DOMContentLoaded', function () {
|
|||
});
|
||||
|
||||
federationSettingsView.render();
|
||||
|
||||
var updateLanguage = function () {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(updateLanguage);
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedLang = $("#languageinput").val(),
|
||||
user = OC.getCurrentUser();
|
||||
|
||||
$.ajax({
|
||||
url: OC.linkToOCS('cloud/users', 2) + user['uid'],
|
||||
method: 'PUT',
|
||||
data: {
|
||||
key: 'language',
|
||||
value: selectedLang
|
||||
},
|
||||
success: function() {
|
||||
location.reload();
|
||||
},
|
||||
fail: function() {
|
||||
OC.Notification.showTemporary(t('settings', 'An error occurred while changing your language. Please reload the page and try again.'));
|
||||
}
|
||||
});
|
||||
};
|
||||
$("#languageinput").change(updateLanguage);
|
||||
|
||||
var updateLocale = function () {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(updateLocale);
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedLocale = $("#localeinput").val(),
|
||||
user = OC.getCurrentUser();
|
||||
|
||||
$.ajax({
|
||||
url: OC.linkToOCS('cloud/users', 2) + user.uid,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
key: 'locale',
|
||||
value: selectedLocale
|
||||
},
|
||||
success: function() {
|
||||
moment.locale(selectedLocale);
|
||||
},
|
||||
fail: function() {
|
||||
OC.Notification.showTemporary(t('settings', 'An error occurred while changing your locale. Please reload the page and try again.'));
|
||||
}
|
||||
});
|
||||
};
|
||||
$("#localeinput").change(updateLocale);
|
||||
});
|
||||
|
||||
window.setInterval(function() {
|
||||
$('#localeexample-time').text(moment().format('LTS'))
|
||||
$('#localeexample-date').text(moment().format('L'))
|
||||
$('#localeexample-fdow').text(t('settings', 'Week starts on {fdow}', { fdow: dayNames[firstDay] }))
|
||||
}, 1000)
|
||||
|
|
|
@ -135,7 +135,6 @@ class PersonalInfo implements ISettings {
|
|||
$totalSpace = \OC_Helper::humanFileSize($storageInfo['total']);
|
||||
}
|
||||
|
||||
$localeParameters = $this->getLocales($user);
|
||||
$messageParameters = $this->getMessageParameters($account);
|
||||
|
||||
$parameters = [
|
||||
|
@ -143,7 +142,7 @@ class PersonalInfo implements ISettings {
|
|||
'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
|
||||
'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
|
||||
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
|
||||
] + $messageParameters + $localeParameters;
|
||||
] + $messageParameters;
|
||||
|
||||
$personalInfoParameters = [
|
||||
'userId' => $uid,
|
||||
|
@ -160,6 +159,7 @@ class PersonalInfo implements ISettings {
|
|||
'website' => $this->getProperty($account, IAccountManager::PROPERTY_WEBSITE),
|
||||
'twitter' => $this->getProperty($account, IAccountManager::PROPERTY_TWITTER),
|
||||
'languageMap' => $this->getLanguageMap($user),
|
||||
'localeMap' => $this->getLocaleMap($user),
|
||||
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
|
||||
'profileEnabled' => $this->profileManager->isProfileEnabled($user),
|
||||
'organisation' => $this->getProperty($account, IAccountManager::PROPERTY_ORGANISATION),
|
||||
|
@ -315,31 +315,24 @@ class PersonalInfo implements ISettings {
|
|||
);
|
||||
}
|
||||
|
||||
private function getLocales(IUser $user): array {
|
||||
private function getLocaleMap(IUser $user): array {
|
||||
$forceLanguage = $this->config->getSystemValue('force_locale', false);
|
||||
if ($forceLanguage !== false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
|
||||
$userLocaleString = $this->config->getUserValue($uid, 'core', 'locale', $this->l10nFactory->findLocale());
|
||||
|
||||
$userLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
|
||||
|
||||
$localeCodes = $this->l10nFactory->findAvailableLocales();
|
||||
|
||||
$userLocale = array_filter($localeCodes, function ($value) use ($userLocaleString) {
|
||||
return $userLocaleString === $value['code'];
|
||||
});
|
||||
$userLocale = array_filter($localeCodes, fn ($value) => $userLocaleString === $value['code']);
|
||||
|
||||
if (!empty($userLocale)) {
|
||||
$userLocale = reset($userLocale);
|
||||
}
|
||||
|
||||
$localesForLanguage = array_filter($localeCodes, function ($localeCode) use ($userLang) {
|
||||
return 0 === strpos($localeCode['code'], $userLang);
|
||||
});
|
||||
$localesForLanguage = array_values(array_filter($localeCodes, fn ($localeCode) => strpos($localeCode['code'], $userLang) === 0));
|
||||
$otherLocales = array_values(array_filter($localeCodes, fn ($localeCode) => strpos($localeCode['code'], $userLang) !== 0));
|
||||
|
||||
if (!$userLocale) {
|
||||
$userLocale = [
|
||||
|
@ -349,10 +342,10 @@ class PersonalInfo implements ISettings {
|
|||
}
|
||||
|
||||
return [
|
||||
'activelocaleLang' => $userLocaleString,
|
||||
'activelocale' => $userLocale,
|
||||
'locales' => $localeCodes,
|
||||
'activeLocaleLang' => $userLocaleString,
|
||||
'activeLocale' => $userLocale,
|
||||
'localesForLanguage' => $localesForLanguage,
|
||||
'otherLocales' => $otherLocales,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
<!--
|
||||
- @copyright 2022 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="locale">
|
||||
<select :id="inputId"
|
||||
:placeholder="t('settings', 'Locale')"
|
||||
@change="onLocaleChange">
|
||||
<option v-for="currentLocale in localesForLanguage"
|
||||
:key="currentLocale.code"
|
||||
:selected="locale.code === currentLocale.code"
|
||||
:value="currentLocale.code">
|
||||
{{ currentLocale.name }}
|
||||
</option>
|
||||
<option disabled>
|
||||
──────────
|
||||
</option>
|
||||
<option v-for="currentLocale in otherLocales"
|
||||
:key="currentLocale.code"
|
||||
:selected="locale.code === currentLocale.code"
|
||||
:value="currentLocale.code">
|
||||
{{ currentLocale.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="example">
|
||||
<Web :size="20" />
|
||||
<div class="example__text">
|
||||
<p>
|
||||
<span>{{ example.date }}</span>
|
||||
<span>{{ example.time }}</span>
|
||||
</p>
|
||||
<p>
|
||||
{{ t('settings', 'Week starts on {firstDayOfWeek}', { firstDayOfWeek: this.example.firstDayOfWeek }) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import moment from '@nextcloud/moment'
|
||||
import Web from 'vue-material-design-icons/Web'
|
||||
|
||||
import { ACCOUNT_SETTING_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js'
|
||||
import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js'
|
||||
import { validateLocale } from '../../../utils/validate.js'
|
||||
import logger from '../../../logger.js'
|
||||
|
||||
export default {
|
||||
name: 'Locale',
|
||||
|
||||
components: {
|
||||
Web,
|
||||
},
|
||||
|
||||
props: {
|
||||
inputId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
locale: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
localesForLanguage: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
otherLocales: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialLocale: this.locale,
|
||||
example: {
|
||||
date: moment().format('L'),
|
||||
time: moment().format('LTS'),
|
||||
firstDayOfWeek: window.dayNames[window.firstDay],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
allLocales() {
|
||||
return Object.freeze(
|
||||
[...this.localesForLanguage, ...this.otherLocales]
|
||||
.reduce((acc, { code, name }) => ({ ...acc, [code]: name }), {})
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
setInterval(this.refreshExample, 1000)
|
||||
},
|
||||
|
||||
methods: {
|
||||
async onLocaleChange(e) {
|
||||
const locale = this.constructLocale(e.target.value)
|
||||
this.$emit('update:locale', locale)
|
||||
|
||||
if (validateLocale(locale)) {
|
||||
await this.updateLocale(locale)
|
||||
}
|
||||
},
|
||||
|
||||
async updateLocale(locale) {
|
||||
try {
|
||||
const responseData = await savePrimaryAccountProperty(ACCOUNT_SETTING_PROPERTY_ENUM.LOCALE, locale.code)
|
||||
this.handleResponse({
|
||||
locale,
|
||||
status: responseData.ocs?.meta?.status,
|
||||
})
|
||||
this.reloadPage()
|
||||
} catch (e) {
|
||||
this.handleResponse({
|
||||
errorMessage: t('settings', 'Unable to update locale'),
|
||||
error: e,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
constructLocale(localeCode) {
|
||||
return {
|
||||
code: localeCode,
|
||||
name: this.allLocales[localeCode],
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse({ locale, status, errorMessage, error }) {
|
||||
if (status === 'ok') {
|
||||
this.initialLocale = locale
|
||||
} else {
|
||||
this.$emit('update:locale', this.initialLocale)
|
||||
showError(errorMessage)
|
||||
logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
|
||||
refreshExample() {
|
||||
this.example = {
|
||||
date: moment().format('L'),
|
||||
time: moment().format('LTS'),
|
||||
firstDayOfWeek: window.dayNames[window.firstDay],
|
||||
}
|
||||
},
|
||||
|
||||
reloadPage() {
|
||||
location.reload()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.locale {
|
||||
display: grid;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
margin: 3px 3px 3px 0;
|
||||
padding: 6px 16px;
|
||||
color: var(--color-main-text);
|
||||
border: 1px solid var(--color-border-dark);
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--icon-triangle-s-dark) no-repeat right 4px center;
|
||||
font-family: var(--font-face);
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.example {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
gap: 0 10px;
|
||||
color: var(--color-text-lighter);
|
||||
|
||||
&::v-deep .material-design-icon {
|
||||
align-self: flex-start;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,88 @@
|
|||
<!--
|
||||
- @copyright 2022 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<HeaderBar :input-id="inputId"
|
||||
:readable="propertyReadable" />
|
||||
|
||||
<template v-if="isEditable">
|
||||
<Locale :input-id="inputId"
|
||||
:locales-for-language="localesForLanguage"
|
||||
:other-locales="otherLocales"
|
||||
:locale.sync="locale" />
|
||||
</template>
|
||||
|
||||
<span v-else>
|
||||
{{ t('settings', 'No locale set') }}
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
import Locale from './Locale.vue'
|
||||
import HeaderBar from '../shared/HeaderBar.vue'
|
||||
|
||||
import { ACCOUNT_SETTING_PROPERTY_ENUM, ACCOUNT_SETTING_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js'
|
||||
|
||||
const { localeMap: { activeLocale, localesForLanguage, otherLocales } } = loadState('settings', 'personalInfoParameters', {})
|
||||
|
||||
export default {
|
||||
name: 'LocaleSection',
|
||||
|
||||
components: {
|
||||
Locale,
|
||||
HeaderBar,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
propertyReadable: ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.LOCALE,
|
||||
localesForLanguage,
|
||||
otherLocales,
|
||||
locale: activeLocale,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputId() {
|
||||
return `account-setting-${ACCOUNT_SETTING_PROPERTY_ENUM.LOCALE}`
|
||||
},
|
||||
|
||||
isEditable() {
|
||||
return Boolean(this.locale)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
section {
|
||||
padding: 10px 10px;
|
||||
|
||||
&::v-deep button:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -106,11 +106,13 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({
|
|||
*/
|
||||
export const ACCOUNT_SETTING_PROPERTY_ENUM = Object.freeze({
|
||||
LANGUAGE: 'language',
|
||||
LOCALE: 'locale',
|
||||
})
|
||||
|
||||
/** Enum of account setting properties to human readable setting properties */
|
||||
export const ACCOUNT_SETTING_PROPERTY_READABLE_ENUM = Object.freeze({
|
||||
LANGUAGE: t('settings', 'Language'),
|
||||
LOCALE: t('settings', 'Locale'),
|
||||
})
|
||||
|
||||
/** Enum of scopes */
|
||||
|
|
|
@ -35,6 +35,7 @@ import LocationSection from './components/PersonalInfo/LocationSection.vue'
|
|||
import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue'
|
||||
import TwitterSection from './components/PersonalInfo/TwitterSection.vue'
|
||||
import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection.vue'
|
||||
import LocaleSection from './components/PersonalInfo/LocaleSection/LocaleSection.vue'
|
||||
import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection.vue'
|
||||
import OrganisationSection from './components/PersonalInfo/OrganisationSection.vue'
|
||||
import RoleSection from './components/PersonalInfo/RoleSection.vue'
|
||||
|
@ -61,6 +62,7 @@ const LocationView = Vue.extend(LocationSection)
|
|||
const WebsiteView = Vue.extend(WebsiteSection)
|
||||
const TwitterView = Vue.extend(TwitterSection)
|
||||
const LanguageView = Vue.extend(LanguageSection)
|
||||
const LocaleView = Vue.extend(LocaleSection)
|
||||
|
||||
new AvatarView().$mount('#vue-avatar-section')
|
||||
new DetailsView().$mount('#vue-details-section')
|
||||
|
@ -71,6 +73,7 @@ new LocationView().$mount('#vue-location-section')
|
|||
new WebsiteView().$mount('#vue-website-section')
|
||||
new TwitterView().$mount('#vue-twitter-section')
|
||||
new LanguageView().$mount('#vue-language-section')
|
||||
new LocaleView().$mount('#vue-locale-section')
|
||||
|
||||
if (profileEnabledGlobally) {
|
||||
const ProfileView = Vue.extend(ProfileSection)
|
||||
|
|
|
@ -74,6 +74,18 @@ export function validateLanguage(input) {
|
|||
&& input.name !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the locale input
|
||||
*
|
||||
* @param {object} input the input
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function validateLocale(input) {
|
||||
return input.code !== ''
|
||||
&& input.name !== ''
|
||||
&& input.name !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate boolean input
|
||||
*
|
||||
|
|
|
@ -98,39 +98,7 @@ script('settings', [
|
|||
<div id="vue-language-section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box personal-settings-locale-box">
|
||||
<?php if (isset($_['activelocale'])) { ?>
|
||||
<form id="locale" class="section">
|
||||
<h3>
|
||||
<label for="localeinput"><?php p($l->t('Locale')); ?></label>
|
||||
</h3>
|
||||
<select id="localeinput" name="lang" data-placeholder="<?php p($l->t('Locale')); ?>">
|
||||
<option value="<?php p($_['activelocale']['code']); ?>">
|
||||
<?php p($l->t($_['activelocale']['name'])); ?>
|
||||
</option>
|
||||
<optgroup label="––––––––––"></optgroup>
|
||||
<?php foreach ($_['localesForLanguage'] as $locale) : ?>
|
||||
<option value="<?php p($locale['code']); ?>">
|
||||
<?php p($l->t($locale['name'])); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<optgroup label="––––––––––"></optgroup>
|
||||
<option value="<?php p($_['activelocale']['code']); ?>">
|
||||
<?php p($l->t($_['activelocale']['name'])); ?>
|
||||
</option>
|
||||
<?php foreach ($_['locales'] as $locale) : ?>
|
||||
<option value="<?php p($locale['code']); ?>">
|
||||
<?php p($l->t($locale['name'])); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div id="localeexample" class="personal-info icon-timezone">
|
||||
<p>
|
||||
<span id="localeexample-date"></span> <span id="localeexample-time"></span>
|
||||
</p>
|
||||
<p id="localeexample-fdow"></p>
|
||||
</div>
|
||||
</form>
|
||||
<?php } ?>
|
||||
<div id="vue-locale-section"></div>
|
||||
</div>
|
||||
<span class="msg"></span>
|
||||
</div>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue