diff --git a/components/admin/Settings.vue b/components/admin/Settings.vue index 6cab384b..3f99d667 100644 --- a/components/admin/Settings.vue +++ b/components/admin/Settings.vue @@ -11,8 +11,9 @@ span.float-left {{timezone.value}} small.float-right.text-danger {{timezone.offset}} - el-form-item(:label="$t('common.title')") - el-input(v-model='title' @blur='save("title", title)') + div.mt-4 {{$t('admin.instance_locale')}} + el-select(v-model='instance_locale') + el-option(v-for='locale in Object.keys(locales)' :key='locale' :label='locales[locale]' :value='locale') el-form-item(:label="$t('common.description')") el-input(v-model='description' @blur='save("description", description)') @@ -45,6 +46,7 @@ import { mapActions, mapState } from 'vuex' import moment from 'moment-timezone' import _ from 'lodash' +import locales from '../../locales/esm' export default { name: 'Settings', @@ -52,12 +54,16 @@ export default { return { queryTz: '', title: $store.state.settings.title, - description: $store.state.settings.description + description: $store.state.settings.description, + locales } }, computed: { ...mapState(['settings']), - ...mapState(['settings']), + instance_locale: { + get () { return this.settings.instance_locale }, + set (value) { this.setSetting({ key: 'instance_locale', value }) } + }, instance_timezone: { get () { return this.settings.instance_timezone }, set (value) { this.setSetting({ key: 'instance_timezone', value }) } @@ -87,7 +93,6 @@ export default { .unshift(current_timezone) .map(tz => ({ value: tz, offset: moment().tz(tz).format('z Z') })) .value() - console.error(ret) return ret } }, diff --git a/locales/esm.js b/locales/esm.js new file mode 100644 index 00000000..777ab87d --- /dev/null +++ b/locales/esm.js @@ -0,0 +1,6 @@ +export default { + it: 'Italiano', + en: 'English', + es: 'Español', + ca: 'Català' +} diff --git a/locales/index.js b/locales/index.js index 5a13a5ff..c0a19fc2 100644 --- a/locales/index.js +++ b/locales/index.js @@ -1,8 +1,6 @@ -import it from './it.json' -import en from './en.json' -import es from './es.json' -import ca from './ca.json' - -export default { - it, en, es, ca +module.exports = { + it: 'Italiano', + en: 'English', + es: 'Español', + ca: 'Català' } diff --git a/locales/it.json b/locales/it.json index 4727f7df..224209dd 100644 --- a/locales/it.json +++ b/locales/it.json @@ -181,7 +181,8 @@ "user_block_confirm": "Sicura di voler bloccare l'utente?", "delete_announcement_confirm": "Sicura di voler eliminare l'annuncio?", "announcement_remove_ok": "Annuncio rimosso", - "announcement_description": "In questa sezione puoi inserire annunci che rimarranno in homepage" + "announcement_description": "In questa sezione puoi inserire annunci che rimarranno in homepage", + "instance_locale": "Lingua di default", }, "auth": { "not_confirmed": "Non abbiamo ancora confermato questa mail...", diff --git a/nuxt.config.js b/nuxt.config.js index 1c68bdb8..ff594d12 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -34,12 +34,12 @@ module.exports = { ** Plugins to load before mounting the App */ plugins: [ + '@/plugins/i18n.js', '@/plugins/element-ui', // UI library -> https://element.eleme.io/#/en-US/ '@/plugins/filters', // text filters, datetime, etc. '@/plugins/vue-awesome', // icon '@/plugins/axios', // axios baseurl configuration - { src: '@/plugins/v-calendar', ssr: false }, // calendar, fix ssr - '@/plugins/i18n.js' + { src: '@/plugins/v-calendar', ssr: false } // calendar, fix ssr ], render: { diff --git a/plugins/element-ui.js b/plugins/element-ui.js index bdcf47cc..0f80701c 100644 --- a/plugins/element-ui.js +++ b/plugins/element-ui.js @@ -7,17 +7,9 @@ import { Container, Footer, Timeline, TimelineItem, Menu, MenuItem, Carousel, CarouselItem } from 'element-ui' import locale from 'element-ui/lib/locale' -// import '../assets/style.scss' - -const locales = { - it: require('element-ui/lib/locale/lang/it'), - en: require('element-ui/lib/locale/lang/en'), - es: require('element-ui/lib/locale/lang/es'), - ca: require('element-ui/lib/locale/lang/ca') -} export default ({ app, store }) => { - locale.use(locales[store.state.locale]) + locale.use(require(`element-ui/lib/locale/lang/${store.state.locale}`)) Vue.use(Button) Vue.use(Carousel) Vue.use(CarouselItem) diff --git a/plugins/i18n.js b/plugins/i18n.js index e0adce62..5a13596f 100644 --- a/plugins/i18n.js +++ b/plugins/i18n.js @@ -1,7 +1,6 @@ import Vue from 'vue' import VueI18n from 'vue-i18n' import merge from 'lodash/merge' -import messages from '../locales' Vue.use(VueI18n) @@ -11,6 +10,14 @@ export default ({ app, store, req }) => { if (req.settings.user_locale) { store.commit('setUserLocale', req.settings.user_locale) } } + const messages = {} + messages[store.state.locale] = require(`../locales/${store.state.locale}.json`) + + // always include en fallback locale + if (store.state.locale !== 'en') { + messages.en = require('../locales/en.json') + } + if (store.state.user_locale) { merge(messages[store.state.locale], store.state.user_locale) } diff --git a/server/api/controller/event.js b/server/api/controller/event.js index b683b08f..588c6e65 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -272,12 +272,9 @@ const eventController = { // return created event to the client res.json(event) - // send notification (mastodon/email) - // only if user is authenticated - if (req.user && !event.recurrent) { - const notifier = require('../../notifier') - notifier.notifyEvent('Create', event.id) - } + // send notifications (mastodon / email) + const notifier = require('../../notifier') + notifier.notifyEvent('Create', event.id) } catch (e) { res.sendStatus(400) debug(e) diff --git a/server/api/controller/settings.js b/server/api/controller/settings.js index 4729bad2..4da39e9f 100644 --- a/server/api/controller/settings.js +++ b/server/api/controller/settings.js @@ -11,6 +11,7 @@ const generateKeyPair = util.promisify(crypto.generateKeyPair) const defaultSettings = { instance_timezone: 'Europe/Rome', + instance_locale: 'en', instance_name: config.title.toLowerCase().replace(/ /g, ''), allow_registration: true, allow_anon_event: true, diff --git a/server/api/controller/user.js b/server/api/controller/user.js index d67dcd66..a8d6362e 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -100,7 +100,7 @@ const userController = { const user = await User.create(req.body) debug(`Sending registration email to ${user.email}`) mail.send(user.email, 'register', { user, config }, req.settings.locale) - mail.send(config.admin_email, 'admin_register', { user, config }, req.settings.locale) + mail.send(config.admin_email, 'admin_register', { user, config }) res.sendStatus(200) } catch (e) { res.status(404).json(e) diff --git a/server/api/mail.js b/server/api/mail.js index dc61c454..d85975be 100644 --- a/server/api/mail.js +++ b/server/api/mail.js @@ -2,12 +2,13 @@ const Email = require('email-templates') const path = require('path') const moment = require('moment-timezone') const config = require('config') -const settings = require('./controller/settings') +const settingsController = require('./controller/settings') const debug = require('debug')('email') const { Task, TaskManager } = require('../taskManager') +const locales = require('../../locales') const mail = { - send (addresses, template, locals, locale) { + send (addresses, template, locals, locale = settingsController.settings.instance_locale) { const task = new Task({ name: 'MAIL', removable: true, @@ -17,8 +18,8 @@ const mail = { TaskManager.add(task) }, - _send (addresses, template, locales, locale) { - debug(`Send ${template} email to ${addresses}`) + _send (addresses, template, locals, locale) { + debug(`Send ${template} email to ${addresses} with locale ${locale}`) const email = new Email({ views: { root: path.join(__dirname, '..', 'emails') }, htmlToText: true, @@ -38,12 +39,13 @@ const mail = { objectNotation: true, syncFiles: false, updateFiles: false, - defaultLocale: settings.locale, - locale: settings.locale, - locales: ['it', 'es', 'en', 'ca'] + defaultLocale: settingsController.settings.instance_locale || 'en', + locale, + locales: Object.keys(locales) }, transport: config.smtp }) + const msg = { template, message: { @@ -51,10 +53,10 @@ const mail = { bcc: config.admin_email }, locals: { - ...locales, + ...locals, locale, config: { title: config.title, baseurl: config.baseurl, description: config.description, admin_email: config.admin_email }, - datetime: datetime => moment.unix(datetime).format('ddd, D MMMM HH:mm') + datetime: datetime => moment.unix(datetime).locale(locale).format('ddd, D MMMM HH:mm') } } return email.send(msg) diff --git a/server/api/models/event.js b/server/api/models/event.js index a1355d3a..e771bcd9 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -1,5 +1,6 @@ const config = require('config') const moment = require('moment-timezone') +const settingsController = require('../controller/settings') // const debug = require('debug')('event:modals') module.exports = (sequelize, DataTypes) => { @@ -39,7 +40,7 @@ module.exports = (sequelize, DataTypes) => { Event.belongsTo(models.event, { as: 'parent' }) } - Event.prototype.toNoteAP = function (username, follower = []) { + Event.prototype.toNoteAP = function (username, locale, follower = []) { const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_')) const tag_links = tags.map(t => { return `` @@ -47,8 +48,8 @@ module.exports = (sequelize, DataTypes) => { const content = `${this.title}
📍 ${this.place.name}
- 📅 ${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}

- ${this.description.length > 300 ? this.description.substr(0, 300) + '...' : this.description}
+ 📅 ${moment.unix(this.start_datetime).locale(locale).format('dddd, D MMMM (HH:mm)')}

+ ${this.description.length > 500 ? this.description.substr(0, 500) + '...' : this.description}
${tag_links}
` const attachment = [] diff --git a/server/federation/helpers.js b/server/federation/helpers.js index 230e6864..13bec2b8 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -80,7 +80,9 @@ const Helpers = { to: recipients[sharedInbox], cc: ['https://www.w3.org/ns/activitystreams#Public', `${config.baseurl}/federation/u/${settingsController.settings.instance_name}/followers`], actor: `${config.baseurl}/federation/u/${settingsController.settings.instance_name}`, - object: event.toNoteAP(settingsController.settings.instance_name, recipients[sharedInbox]) + object: event.toNoteAP(settingsController.settings.instance_name, + settingsController.settings.instance_locale, + recipients[sharedInbox]) } body['@context'] = [ 'https://www.w3.org/ns/activitystreams', diff --git a/server/federation/index.js b/server/federation/index.js index 20eb485b..b96809a2 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -33,7 +33,7 @@ router.get('/m/:event_id', async (req, res) => { const event = await Event.findByPk(req.params.event_id, { include: [User, Tag, Place] }) if (!event) { return res.status(404).send('Not found') } - return res.json(event.toNoteAP(event.user.username)) + return res.json(event.toNoteAP(event.user.username, req.settings.locale)) }) // get any message coming from federation diff --git a/server/federation/users.js b/server/federation/users.js index 1cfd320e..630b8ca6 100644 --- a/server/federation/users.js +++ b/server/federation/users.js @@ -115,7 +115,7 @@ module.exports = { type: 'OrderedCollectionPage', totalItems: events.length, partOf: `${config.baseurl}/federation/u/${name}/outbox`, - orderedItems: events.map(e => ({ ...e.toNoteAP(name), actor: `${config.baseurl}/federation/u/${name}` })) + orderedItems: events.map(e => ({ ...e.toNoteAP(name, req.settings.locale), actor: `${config.baseurl}/federation/u/${name}` })) // user.events.map(e => ({ // id: `${config.baseurl}/federation/m/${e.id}#create`, // type: 'Create', diff --git a/server/firstrun.js b/server/firstrun.js index 59f8df8b..ae2c2d03 100644 --- a/server/firstrun.js +++ b/server/firstrun.js @@ -60,15 +60,15 @@ module.exports = { // announcement: 'TODO: HTML First presentation post' // }) - // send confirmed event to mastodon + // send confirmed events to mastodon await db.notification.create({ action: 'Create', type: 'ap', filters: { is_visible: true } }) await db.notification.create({ action: 'Update', type: 'ap', filters: { is_visible: true } }) await db.notification.create({ action: 'Delete', type: 'ap', filters: { is_visible: true } }) - // send anon event to administrator + // send anon events to admin await db.notification.create({ action: 'Create', type: 'admin_email', filters: { is_visible: false } }) - // TODO + // TODO email's notifications // await db.notification.create({ action: 'Create', type: 'email', filters: { is_visible: true } }) // close db connection diff --git a/server/helpers.js b/server/helpers.js index 7def7283..ea742192 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -9,6 +9,7 @@ const { JSDOM } = require('jsdom') const { window } = new JSDOM('') const domPurify = DOMPurify(window) const URL = require('url') +const locales = require('../locales') domPurify.addHook('beforeSanitizeElements', node => { if (node.hasAttribute && node.hasAttribute('href')) { @@ -52,8 +53,7 @@ module.exports = { // set locale and user locale const acceptedLanguages = req.headers['accept-language'] - const supportedLanguages = ['en', 'it', 'es', 'ca'] - acceptLanguage.languages(supportedLanguages) + acceptLanguage.languages(Object.keys(locales)) req.settings.locale = acceptLanguage.get(acceptedLanguages) req.settings.user_locale = settingsController.user_locale[req.settings.locale] moment.locale(req.settings.locale) diff --git a/server/notifier.js b/server/notifier.js index 40d7ee20..4961efb0 100644 --- a/server/notifier.js +++ b/server/notifier.js @@ -15,10 +15,11 @@ const notifier = { debug('Send %s notification %s', notification.type, notification.action) let p switch (notification.type) { - case 'mail': - return mail.send(notification.email, 'event', { event, config, notification }) + // case 'mail': TODO: locale? + // return mail.send(notification.email, 'event', { event, notification }) case 'admin_email': - p = mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) + p = mail.send(config.admin_email, 'event', + { event, to_confirm: !event.is_visible, notification }) promises.push(p) break case 'ap':