From c553781033ca9f803a22f678ba3267d96d1d8070 Mon Sep 17 00:00:00 2001 From: MomentQYC Date: Mon, 6 Mar 2023 13:28:36 +0800 Subject: [PATCH 1/8] 'feat:i18n(#13)' --- app.js | 44 ++++- config/api-example.js | 3 +- helpers.js | 20 ++- locales/en-US.json | 158 ++++++++++++++++++ package-lock.json | 1 + package.json | 1 + routes.js | 2 +- views/404.handlebars | 4 +- views/event.handlebars | 142 ++++++++-------- views/eventgroup.handlebars | 74 ++++---- views/home.handlebars | 12 +- views/layouts/main.handlebars | 2 +- views/login.handlebars | 16 +- views/newevent.handlebars | 18 +- views/optionsform.handlebars | 16 +- views/partials/editeventgroupmodal.handlebars | 30 ++-- views/partials/editeventmodal.handlebars | 73 ++++---- views/partials/importeventform.handlebars | 14 +- views/partials/neweventform.handlebars | 78 ++++----- views/partials/neweventgroupform.handlebars | 42 ++--- views/partials/sidebar-dropdown.handlebars | 8 +- views/partials/sidebar.handlebars | 6 +- views/register.handlebars | 8 +- 23 files changed, 489 insertions(+), 283 deletions(-) create mode 100644 locales/en-US.json diff --git a/app.js b/app.js index c6e0647..a21ca73 100755 --- a/app.js +++ b/app.js @@ -5,16 +5,22 @@ const cors = require('cors'); const routes = require('./routes'); const hbs = require('express-handlebars'); const bodyParser = require('body-parser'); +const i18n = require('i18n'); const app = express(); - // Configuration // - //app.use(cors()); //app.use(bodyParser.json()); //app.use(session({ secret: 'slartibartfast', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); - - +app.use(session({ + secret: 'Py0Bf3aWZC8kYkYTpRztmYMyS22pFFGi' +})); +i18n.configure({ + locales:['en-US'], //声明包含的语言 + directory: __dirname + '/locales', //翻译json文件的路径 + defaultLocale: 'en-US' //默认的语言,即为上述标准4 +}); +app.use(i18n.init); // View engine // hbsInstance = hbs.create({ defaultLayout: 'main', @@ -51,4 +57,34 @@ app.use(bodyParser.json({ type: "application/activity+json" })); // support json app.use(bodyParser.urlencoded({ extended: true })); app.use('/', routes); +app.use(setLocale); module.exports = app; + +// 定义setLocale中间件 +function setLocale(req, res, next){ + var locale; + // 当req进入i18n中间件的时候,已经通过sessionId信息获取了用户数据 + // 获取用户数据中的locale数据 + if(req.user){ + locale = req.user.locale; + } + // 获取cookie中的locale数据 + else if(req.signedCookies['locale']){ + locale = req.signedCookies['locale']; + } + // 获取浏览器第一个偏好语言,这个函数是express提供的 + else if(req.acceptsLanguages()){ + locale = req.acceptsLanguages(); + } + // 没有语言偏好的时候网站使用的语言为中文 + else{ + locale = 'en-US'; + } + // 如果cookie中保存的语言偏好与此处使用的语言偏好不同,更新cookie中的语言偏好设置 + if(req.signedCookies['locale'] !== locale){ + res.cookie('locale', locale, { signed: true, httpOnly: true }); + } + // 设置i18n对这个请求所使用的语言 + req.setLocale(locale); + next(); +}; \ No newline at end of file diff --git a/config/api-example.js b/config/api-example.js index 9202f0a..4f66ff8 100644 --- a/config/api-example.js +++ b/config/api-example.js @@ -4,5 +4,6 @@ module.exports = { 'smtpServer': '', // If using Nodemailer, your SMTP server hostname goes here 'smtpPort': '', // If using Nodemailer, your SMTP server port goes here 'smtpUsername': '', // If using Nodemailer, your SMTP server username goes here - 'smtpPassword': '' // If using Nodemailer, your SMTP password goes here + 'smtpPassword': '', // If using Nodemailer, your SMTP password goes here + 'smtpSecure': true // true for 465, false for other ports }; diff --git a/helpers.js b/helpers.js index bf95e27..1cd638f 100644 --- a/helpers.js +++ b/helpers.js @@ -5,7 +5,7 @@ const mongoose = require('mongoose'); const Log = mongoose.model('Log'); var moment = require('moment-timezone'); const icalGenerator = require('ical-generator'); - +var i18n = require('i18n'); // LOGGING function addToLog(process, status, message) { @@ -51,7 +51,23 @@ function exportIcal(events, calendarName) { return string; } +function getI18nHelpers() { + var _helpers = {}; + // 声明handlebar中的i18n helper函数 + // __函数不考虑单复数 + _helpers.__ = function () { + return i18n.__.apply(this, arguments); + }; + // __n函数考虑单复数 + _helpers.__n = function () { + return i18n.__n.apply(this, arguments); + }; + + return _helpers; +} + module.exports = { addToLog, exportIcal, -} + getI18nHelpers, +} \ No newline at end of file diff --git a/locales/en-US.json b/locales/en-US.json new file mode 100644 index 0000000..5df38e0 --- /dev/null +++ b/locales/en-US.json @@ -0,0 +1,158 @@ +{ + "notfound": "Event not found!", + "404desc": "It may have never existed, or if it finished more than a week ago, it's been removed from the server. Don't despair - why not create a new one? I for one would love to come to your ocarina recital.", + "eventedit": "Edit event", + "started": "Started", + "ended": "Ended", + "hostedby": "Hosted by {{eventData.hostName}}", + "partof": "Part of {{eventData.eventGroup.name}}", + "copy": "Copy", + "addtoGC": "Add to Google Calendar", + "ICSexport": "Export as ICS", + "showonGM": "Show on Google Maps", + "showonOM": "Show on OpenStreetMap", + "concludeddel": "This event has concluded. It can no longer be edited, and will be automatically deleted .", + "eventwelcome": "Welcome to your event!", + "eventattention": "Your secret editing password for this event is: {{eventData.editToken}}. It's been saved in your browser storage, and if you supplied your email, it's also been sent to you. If you didn't supply your email, you must save it somewhere safe, because it won't be shown again!", + "eventshare": "To share your event, use the link you can see just above this message - that way your attendees won't be able to edit or delete your event!", + "about": "About", + "attendees": "Attendees", + "addme": "Add me", + "removeme": "Remove me", + "eventcapacity": "This event is at capacity.", + "remaining": "remaining - add yourself now!", + "removeuser": "Remove user from event", + "noattendees": "No attendees yet!", + "addself": "Add yourself to '{{eventData.name}}'", + "joinname": "Your name", + "peoplenum": "How many people in your party?", + "joinemail": "Your Email (optional)", + "joinemaildesc": "If you provide your email, you will receive updates to the event.", + "notspam": "We won't spam you", + "delpasswd": "Deletion password", + "delpwddesc": "You will need this password if you want to remove yourself from the list of event attendees. If you provided your email, you'll receive it by email. Otherwise, write it down now because it will not be shown again.", + "close": "Close", + "addmyself": "Add myself", + "selfremove": "Remove yourself from '{{eventData.name}}'", + "pwddel": "Your deletion password", + "lostpwd": "Lost your password? Get in touch with the event organiser.", + "rmmyself": "Remove myself", + "rmattendee": "Remove attendee from '{{eventData.name}}'", + "confirmrmA": "Are you sure you want to remove this attendee from the event? This action cannot be undone.", + "rmA": "Remove attendee", + "discussion": "Discussion", + "send": "Send", + "reply": "Reply", + "del": "Delete", + "editpwd": "Enter editing password", + "editpwddesc": "Enter the editing password you received by email or were shown when the event was created.", + "eventdelconfirm": "Are you sure you want to delete this event? This action cannot be undone.", + "eventdel": "Delete event", + "copied": "Copied!", + "incorrectpwd": "That editing password is incorrect. Try again.", + "numlimit": "Please enter a number between 1 and ${response.data.freeSpots}", + "editgroup": "Edit group", + "groupwelcome": "Welcome to your event group! We've just sent you an email with your secret editing link, which you can also see in the address bar above. Haven't got the email? Check your spam or junk folder. To share your event group, use the link you can see just below this message - that way your attendees won't be able to edit or delete your event group!", + "urlpaste": "Paste this URL into your calendar app\nto subscribe to a live feed of events from this group.", + "subscribe": "Subscribe to updates", + "groupaddevent": "To add an event to this group, copy and paste the two codes below into the 'Event Group' box when creating a new event or editing an existing event.", + "eventgroupid": "Event group ID", + "eventgroupeditpwd": "Event group editing password", + "eventupcome": "Upcoming events", + "noevents": "No events!", + "groupdelconfirm": "Are you sure you want to delete this event group? This action cannot be undone.", + "groupdeldesc": "This will not delete the individual events contained in this group. They can be linked to another group later.", + "groupdel": "Delete event group", + "groupsub": "Subscribe to '{{eventGroupData.name}}'", + "receivegroup": "Enter your email address to receive updates\nwhenever a new event is created in this group.", + "sub": "Subscribe", + "intro": "A quick and easy way to make and share events which respects your privacy.", + "desc": "You don't need to sign up for an account. When you create an event, we generate a password which allows you to edit the event. Send all your guests the public link, and all your co-hosts the secret editing link containing the password. A week after the event finishes, it's deleted from our servers for ever, and your email goes with it.", + "privacy": "Also, we don't show you ads, don't sell your data, and never send you unnecessary emails.", + "attention": "But remember: all our events are visible to anyone who knows the link, so probably don't use gathio to plot your surprise birthday party or revolution. Or whatever, you do you.", + "kofi": "If you find yourself using and enjoying gathio, consider buying me a coffee. It'll help keep the site running!", + "support": "Support Me on Ko-fi", + "accountdesc": "Register or log in to your account to be able to show this event on your profile, edit it, and delete it. Associating an event with an account is optional, but keep in mind that an unassociated event can not be modified in any way and will be automatically deleted 30 days after it concludes.", + "paccount": "You must register or log in to your account to create a private event.", + "oaccount": "You must register or log in to an organisation account to create an organisation event.", + "emailaddr": "Email address", + "password": "Password", + "register": "Register", + "forgotpwd": "Forgot password", + "neweventpage": "New event", + "newevent": "Events are visible to anyone who knows the link.", + "createnew": "Create a new event", + "importnew": "Import an existing event", + "newgroup": "Create a new event group", + "options": "Options", + "selfmark": "Users can mark themselves as attending this event", + "displaymod": "{{#if isPrivate}}Privately display{{else}}Publicly display{{/if}} the list of attendees", + "commentmod": "Users can post comments on this event", + "listdisplay": "Display the list of attendees", + "edit": "Edit", + "name": "Name", + "description": "Description", + "link": "Link", + "host": "Host or organisation name", + "coverimg": "Cover image", + "recommendeddimensions": "Recommended dimensions (w x h): 920px by 300px.", + "delgroup": "Delete this event group", + "delegroup": "Delete event group", + "save": "Save changes", + "eventname": "Event name", + "location": "Location", + "starts": "Starts", + "ends": "Ends", + "timezone": "Timezone", + "mdsupport": "Markdown formatting\nsupported.", + "hostname": "Host name", + "choosefile": "Choose file", + "imgdel": "Delete image", + "grouppart": "This event is part of an event group", + "linktogroup": "Link this event to an event group", + "shortstringdesc": "You can find this short string of characters in the\nevent group's link, in your confirmation email, or on the event group's\npage.", + "groupsecretcode": "Event group secret\nediting code", + "longstringdesc": "You can find this long string of characters in the\nconfirmation email you received when you created the event group.", + "limitset": "Set a limit on the maximum number of attendees", + "attendeelimit": "Attendee limit", + "delevent": "Delete this event", + "importevent": "Import an existing event", + "importdesc": "Upload an .ics file here to instantly create an event. You can save a Facebook event as an .ics file by clicking on the context menu next to the 'Import' and 'Edit' buttons on the event page and choosing the 'Export Event' option. Then select the 'Save to calendar' option and save the file on your computer.", + "selectfile": "Select file", + "youremail": "Your email", + "emaildesc": "We will send your secret editing link to this email address.", + "import": "Import", + "createevent": "Create an event", + "mdsupportA": "Markdown formatting supported.", + "eventpwd": "Event password", + "emaildescA": "If you provide your email, we will send your secret editing password here, and use it to notify you of updates to the event.", + "shortstringdescA": "You can find this short string of characters in the event group's link, in your confirmation email, or on the event group's page.", + "groupeditpwd": "Event group secret editing code", + "longstringdescA": "You can find this long string of characters in the confirmation email you received when you created the event group.", + "changefile": "Change file", + "groupcreate": "Create an event group", + "groupdesc": "An event group is a holding area for a set of linked events, like a series of film nights, a festival, or a band tour. You can share a public link to your event group just like an individual event link, and people who know the secret editing code (sent in an email when you create the event group) will be able to add future events to the group.", + "groupattention": "Event groups do not get automatically removed like events do, but events which have been removed from {{siteName}} will of course not show up in an event group.", + "groupname": "Event group name", + "private": "Private", + "public": "Public", + "org": "Organization", + "nicerevent": "Nicer events", + "create": "Create", + "or": "Or an alias, perhaps...", + "eventask": "What would you like to ask?", + "eventreply": "What would you like to reply?", + "right": "Get it right!", + "notspamlogin": "We will never spam you or share your email.", + "notforget": "Don't forget it!", + "snappy": "Make it snappy.", + "moreinfo": "For tickets or a page with more information (optional).", + "isshowgroup": "Will be shown on the event group page (optional).", + "specific": "Be specific.", + "anotherpage": "For tickets or another event page (optional).", + "isshowpage": "Will be shown on the event page (optional).", + "enternum": "Enter a number.", + "click": "Click me!", + "editlater": "You can always edit it later.", + "notspamA": "We won't spam you <3 (optional)" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b19767b..b0a30bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "greenlock-express": "^2.7.18", "ical": "^0.6.0", "ical-generator": "^1.11.0", + "i18n": "^0.15.1", "jimp": "^0.16.1", "jsonwebtoken": "^9.0.0", "marked": "^4.0.10", diff --git a/package.json b/package.json index 8963fd2..127195a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "generate-rsa-keypair": "^0.2.1", "greenlock": "^2.8.8", "greenlock-express": "^2.7.18", + "i18n": "^0.15.1", "ical": "^0.6.0", "ical-generator": "^1.11.0", "jimp": "^0.16.1", diff --git a/routes.js b/routes.js index c9867e3..05b2919 100755 --- a/routes.js +++ b/routes.js @@ -103,7 +103,7 @@ if (mailService) { nodemailerTransporter = nodemailer.createTransport({ host: apiCredentials.smtpServer, port: apiCredentials.smtpPort, - secure: false, // true for 465, false for other ports + secure: apiCredentials.smtpSecure, // true for 465, false for other ports auth: { user: apiCredentials.smtpUsername, // generated ethereal user pass: apiCredentials.smtpPassword, // generated ethereal password diff --git a/views/404.handlebars b/views/404.handlebars index 832f81a..128dcdd 100755 --- a/views/404.handlebars +++ b/views/404.handlebars @@ -1,5 +1,5 @@

404

-

Event not found!

+

{{{__ "notfound"}}}

-

It may have never existed, or if it finished more than a week ago, it's been removed from the server. Don't despair - why not create a new one? I for one would love to come to your ocarina recital.

+

{{{__ "404desc"}}}

diff --git a/views/event.handlebars b/views/event.handlebars index 88856f3..c787c68 100755 --- a/views/event.handlebars +++ b/views/event.handlebars @@ -10,9 +10,9 @@
{{#if editingEnabled}} - + {{else}} - + {{/if}}
@@ -36,7 +36,7 @@
- {{#if eventHasBegun}}{{#unless eventHasConcluded}}Started {{else}}Ended {{/unless}}{{/if}}{{fromNow}} + {{#if eventHasBegun}}{{#unless eventHasConcluded}}{{{__ "started"}}} {{else}}{{{__ "ended"}}} {{/unless}}{{/if}}{{fromNow}} {{#if eventHasHost}} @@ -44,7 +44,7 @@ - Hosted by {{eventData.hostName}} + {{{__ "hostedby"}}} {{/if}} {{#if eventData.eventGroup}} @@ -52,7 +52,7 @@ - Part of {{eventData.eventGroup.name}} + {{{__ "partof"}}} {{/if}} {{#if eventData.url}} @@ -71,7 +71,7 @@ https://{{domain}}/{{eventData.id}} {{#if isFederated}} @@ -81,7 +81,7 @@ @{{eventData.id}}@{{domain}} {{/if}} @@ -90,17 +90,17 @@
@@ -109,7 +109,7 @@ {{#if eventHasConcluded}} {{/if}} {{#if firstLoad}} @@ -117,13 +117,13 @@ -

Welcome to your event!

-

Your secret editing password for this event is: {{eventData.editToken}}. It's been saved in your browser storage, and if you supplied your email, it's also been sent to you. If you didn't supply your email, you must save it somewhere safe, because it won't be shown again!

-

To share your event, use the link you can see just above this message - that way your attendees won't be able to edit or delete your event!

+

{{{__ "eventwelcome"}}}

+

{{{__ "eventattention"}}}

+

{{{__ "eventshare"}}}

{{/if}}
-
About
+
{{{__ "about"}}}
{{{parsedDescription}}}
@@ -131,30 +131,30 @@ {{#if eventData.usersCanAttend}}
-
Attendees {{#if eventAttendees}}({{numberOfAttendees}}){{/if}} +
{{{__ "attendees"}}} {{#if eventAttendees}}({{numberOfAttendees}}){{/if}}
{{#unless noMoreSpots}} - + {{/unless}} - +
{{#if eventData.maxAttendees}} {{#if noMoreSpots}} -
This event is at capacity.
+
{{{__ "eventcapacity"}}}
{{else}} -
{{spotsRemaining}} {{plural spotsRemaining "spot(s)"}} remaining - add yourself now!
+
{{spotsRemaining}} {{plural spotsRemaining "spot(s)"}} {{{__ "remaining"}}}
{{/if}} {{/if}} {{#if eventAttendees}}
    {{#each eventAttendees}} - {{this.name}}{{#if ../editingEnabled}} {{/if}} + {{this.name}}{{#if ../editingEnabled}} {{/if}} {{/each}}
{{else}} -

No attendees yet!

+

{{{__ "noattendees"}}}

{{/if}}
@@ -163,7 +163,7 @@