diff options
-rw-r--r-- | locales/en-US.json | 13 | ||||
-rw-r--r-- | locales/en.json | 13 | ||||
-rw-r--r-- | locales/ja.json | 19 | ||||
-rw-r--r-- | src/lib/event.ts | 4 | ||||
-rw-r--r-- | src/routes/frontend.ts | 91 | ||||
-rwxr-xr-x | views/event.handlebars | 4 |
6 files changed, 102 insertions, 42 deletions
diff --git a/locales/en-US.json b/locales/en-US.json index a02c500..d397809 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -11,6 +11,7 @@ "create": "Create", "creating": "Creating...", "creatoremaildesc": "If you provide your email, we will send your secret editing password here, and use it to notify you of updates to the event.", + "datetimeformat": "{{thedate, intlDate}}", "del": "Delete", "edittoken": "Enter editing password", "edittokendesc": "Enter the editing password you received by email or were shown when the event was created.", @@ -31,6 +32,8 @@ "event.attention": "Your secret editing password for this event is: <strong>{{eventData.editToken}}</strong>. 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 <strong>must save it somewhere safe</strong>, because it won't be shown again!", "event.capacity": "This event is at capacity.", "event.comment": "Comment", + "event.commentauthor": "Name", + "event.commentauthorph": "Your name", "event.commentcontent": "What would you like to say?", "event.concludeddel": " This event has concluded. It can no longer be edited{{#if eventWillBeDeleted}}, and will be automatically deleted {{daysUntilDeletion}}{{/if}}.", "event.confremoveattendee": "Are you sure you want to remove this attendee from the event? This action cannot be undone.", @@ -73,7 +76,7 @@ "event.p.hostname": "Host name", "event.p.hostnamedesc": "Will be shown on the event page (optional).", "event.p.maxattendees": "Attendee limit", - "event.p.maxattendeestitle": "This event is at capacity.", + "event.p.maxattendeestitle": "Set a limit on the maximum number of attendees", "event.p.publicevent": "Display this event on the public event list", "event.p.timezone": "Timezone", "event.partof": "<a href='/group/{{eventData.eventGroup.id}}'>{{eventData.eventGroup.name}}</a>", @@ -101,8 +104,14 @@ "eventpwd": "Event password", "fixerrors": "Please fix these errors:", "forgotpwd": "Forgot password", + "frontend.dateformat": "LL", + "frontend.displaydate-days": "{{ startdate }} <span class=\"text-muted\">at</span> {{ starttime }} <span class=\"text-muted\">-</span> {{ enddate }} <span class=\"text-muted\">at</span> {{ endtime }} {{ timezone }}", + "frontend.displaydate-sameday": "{{ startdate }} <span class=\"text-muted\">from<span> {{ starttime }} <span class=\"text-muted\">to</span> {{ endtime }}{{ timezone}}", "frontend.elnumber": "({{count}} people)", "frontend.eventattendees": "people", + "frontend.publicevents": "Public events", + "frontend.sameday.to": "<span class=\"text-muted\"> to </span>", + "frontend.timeformat": "LT", "group.about": "About", "group.addevent": "To link an existing 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.", "group.del": "Delete this event group", @@ -157,6 +166,7 @@ "interaction": "Users can post comments on this event", "join": "Users can mark themselves as attending this event", "joinemaildesc": "If you provide your email, you will receive updates to the event.", + "magiclink-invalid": "This magic link is invalid or has expired. Please request a new one here.", "main.defaultmetadata": "An easier, quicker, and much less privacy-invading way to make and share events", "main.footnote": " <strong>Gathio</strong> version {{version}} · <a href=\"https://github.com/lowercasename/gathio\">GitHub</a> · Made with <i class=\"far fa-heart\"></i> by <a href=\"https://raphaelkabo.com\">Raphael</a> and <a href=\"https://github.com/lowercasename/gathio/graphs/contributors\">contributors</a>", "mdsupport": "<a href='https://commonmark.org/help/'>Markdown</a> formatting\nsupported.", @@ -195,5 +205,6 @@ "snappy": "Make it snappy.", "upcomingevents": "Upcoming events", "wontshow": "Will not be shown anywhere (optional).", + "year-month-format": "MMMM YYYY", "youremail": "Your email" }
\ No newline at end of file diff --git a/locales/en.json b/locales/en.json index a02c500..d397809 100644 --- a/locales/en.json +++ b/locales/en.json @@ -11,6 +11,7 @@ "create": "Create", "creating": "Creating...", "creatoremaildesc": "If you provide your email, we will send your secret editing password here, and use it to notify you of updates to the event.", + "datetimeformat": "{{thedate, intlDate}}", "del": "Delete", "edittoken": "Enter editing password", "edittokendesc": "Enter the editing password you received by email or were shown when the event was created.", @@ -31,6 +32,8 @@ "event.attention": "Your secret editing password for this event is: <strong>{{eventData.editToken}}</strong>. 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 <strong>must save it somewhere safe</strong>, because it won't be shown again!", "event.capacity": "This event is at capacity.", "event.comment": "Comment", + "event.commentauthor": "Name", + "event.commentauthorph": "Your name", "event.commentcontent": "What would you like to say?", "event.concludeddel": " This event has concluded. It can no longer be edited{{#if eventWillBeDeleted}}, and will be automatically deleted {{daysUntilDeletion}}{{/if}}.", "event.confremoveattendee": "Are you sure you want to remove this attendee from the event? This action cannot be undone.", @@ -73,7 +76,7 @@ "event.p.hostname": "Host name", "event.p.hostnamedesc": "Will be shown on the event page (optional).", "event.p.maxattendees": "Attendee limit", - "event.p.maxattendeestitle": "This event is at capacity.", + "event.p.maxattendeestitle": "Set a limit on the maximum number of attendees", "event.p.publicevent": "Display this event on the public event list", "event.p.timezone": "Timezone", "event.partof": "<a href='/group/{{eventData.eventGroup.id}}'>{{eventData.eventGroup.name}}</a>", @@ -101,8 +104,14 @@ "eventpwd": "Event password", "fixerrors": "Please fix these errors:", "forgotpwd": "Forgot password", + "frontend.dateformat": "LL", + "frontend.displaydate-days": "{{ startdate }} <span class=\"text-muted\">at</span> {{ starttime }} <span class=\"text-muted\">-</span> {{ enddate }} <span class=\"text-muted\">at</span> {{ endtime }} {{ timezone }}", + "frontend.displaydate-sameday": "{{ startdate }} <span class=\"text-muted\">from<span> {{ starttime }} <span class=\"text-muted\">to</span> {{ endtime }}{{ timezone}}", "frontend.elnumber": "({{count}} people)", "frontend.eventattendees": "people", + "frontend.publicevents": "Public events", + "frontend.sameday.to": "<span class=\"text-muted\"> to </span>", + "frontend.timeformat": "LT", "group.about": "About", "group.addevent": "To link an existing 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.", "group.del": "Delete this event group", @@ -157,6 +166,7 @@ "interaction": "Users can post comments on this event", "join": "Users can mark themselves as attending this event", "joinemaildesc": "If you provide your email, you will receive updates to the event.", + "magiclink-invalid": "This magic link is invalid or has expired. Please request a new one here.", "main.defaultmetadata": "An easier, quicker, and much less privacy-invading way to make and share events", "main.footnote": " <strong>Gathio</strong> version {{version}} · <a href=\"https://github.com/lowercasename/gathio\">GitHub</a> · Made with <i class=\"far fa-heart\"></i> by <a href=\"https://raphaelkabo.com\">Raphael</a> and <a href=\"https://github.com/lowercasename/gathio/graphs/contributors\">contributors</a>", "mdsupport": "<a href='https://commonmark.org/help/'>Markdown</a> formatting\nsupported.", @@ -195,5 +205,6 @@ "snappy": "Make it snappy.", "upcomingevents": "Upcoming events", "wontshow": "Will not be shown anywhere (optional).", + "year-month-format": "MMMM YYYY", "youremail": "Your email" }
\ No newline at end of file diff --git a/locales/ja.json b/locales/ja.json index 6d7de9e..44c0b63 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -11,6 +11,7 @@ "create": "作成", "creating": "作成中...", "creatoremaildesc": "メールアドレスを入力すると、編集用秘密パスワードを送信します。またイベントについての更新情報も送信します。", + "datetimeformat": "{{thedate, long}}", "del": "削除", "edittoken": "編集パスワードを入力します", "edittokendesc": "編集パスワードを入力してください。イベントが作成時に表示したほか、メールアドレスを入力していたらメールでも送信しています。", @@ -31,6 +32,8 @@ "event.attention": "このイベントの編集用秘密パスワード : <strong>{{eventData.editToken}}</strong></br>ブラウザストレージに保存してあり、メールアドレスを入力したのならメールでも送信しています。メールアドレスを入力しなかったのなら、<strong>すぐに安全な場所に保存</strong>してください。もう二度と表示しませんので!", "event.capacity": "このイベントは満員です。", "event.comment": "コメント", + "event.commentauthor": "お名前", + "event.commentauthorph": "お名前をお願いします", "event.commentcontent": "コメントをどうぞ", "event.concludeddel": "このイベントは終了しました。編集もできなくなりました。{{#if eventWillBeDeleted}}また {{daysUntilDeletion}} 日後に自動的に削除します。{{/if}}", "event.confremoveattendee": "この参加者をイベントから削除します。よろしいですか? この操作は取り消しできません。", @@ -73,7 +76,7 @@ "event.p.hostname": "主催者名", "event.p.hostnamedesc": "イベントのページに表示します ( 任意 )。", "event.p.maxattendees": "定員", - "event.p.maxattendeestitle": "満員です。", + "event.p.maxattendeestitle": "定員を設定する", "event.p.publicevent": "このイベントを公開イベントリストに表示", "event.p.timezone": "タイムゾーン", "event.partof": "<a href='/group/{{eventData.eventGroup.id}}'>{{eventData.eventGroup.name}}</a> グループのイベント", @@ -95,14 +98,20 @@ "event.share": "イベントを共有するには、このメッセージのすぐ上に表示しているリンクをご利用ください。参加者にはイベントを編集したり削除したりすることはできません。", "event.showonGM": "Google マップで表示", "event.showonOM": "OpenStreetMap で表示", - "event.started": "開始済み", + "event.started": "開催中", "event.welcome": "あなたが作成したイベントです。ようこそ!", "eventgroups": "イベントグループ", "eventpwd": "イベントパスワード", "fixerrors": "エラーを修正してください :", "forgotpwd": "パスワードがわからない?", + "frontend.dateformat": "LL (dd)", + "frontend.displaydate-days": "{{ startdate }} {{ starttime }} <span class=\"text-muted\">-</span> {{ enddate }} {{ endtime }} {{ timezone }}", + "frontend.displaydate-sameday": "{{ startdate }} {{ starttime }} <span class=\"text-muted\">~</span> {{ endtime }}{{ timezone}}", "frontend.elnumber": "( {{count}} 人 )", "frontend.eventattendees": "人で", + "frontend.publicevents": "公開イベント", + "frontend.sameday.to": " <span class=\"text-muted\"> ~ </span> ", + "frontend.timeformat": "LT", "group.about": "このグループについて", "group.addevent": "このグループにイベントをリンクするには、新しくイベントを作る際に、もしくは既存のイベントを編集して、以下の 2 つのコードをコピー・貼り付けします。", "group.del": "このイベントグループを削除", @@ -140,7 +149,7 @@ "home.fedtitle": "連合プロトコルとセルフホスト", "home.flagshipsetting": "<a href = \"https://gath.io\">Gathio のフラッグシップインスタンスは gath.io </a>です。終わり次第順次削除されるイベント、URL を知っている人にしか開けないイベントを、だれでも作成できるよう設計しています。\nもう一度言います、ここではだれでもイベントを作成できます。イベントがパブリックな場所に公開されることはありません。そしてイベント終了の 7 日後に削除します。", "home.imgexample": "ピクニックに行くイベントページの例です。イベントの場所、主催、日時と説明を記載しています。また Google カレンダーに保存、エクスポート、場所を OpenStreetMap と Google マップから開くこともできます。", - "home.intro": "Gathio は、簡単、プライバシー・ファーストでイベントを作成・共有するシステムです。連合プロトコルにも対応しています。", + "home.intro": "Gathio は、簡単、プライバシー・ファーストで、イベントを作成・共有するシステムです。連合プロトコルにも対応しています。", "home.kofi": "Ko-fi で支援を", "home.kofidesc": "<strong>gath<span class='text-muted'>io</span></strong> 、よく使ってるし便利だよね…と思ったら、コーヒー 1 杯分を出してくれませんか? かならずサイトの運営に役立てます! <i class=\"far fa-heart\"></i>", "home.onpre": "あなたのコミュニティーで自分たちの Gathio インスタンスを建てるなら、そこではイベントの作成を特定の人しかできないようにしたり、ホームページに便利なイベント一覧を表示したり、イベント削除を一切しない…といった制限・設定を加えることもできます。", @@ -157,6 +166,7 @@ "interaction": "ユーザーにコメントを許可する", "join": "ユーザーは自分で参加登録する", "joinemaildesc": "メールアドレスを入力しておくと、このイベントについての情報を送信します。", + "magiclink-invalid": "このマジックリンクは無効、または期限が切れています。再度リクエストしてください。", "main.defaultmetadata": "より簡単に、より速く、よりプライバシー侵害の小さい、イベントを作成・共有する方法", "main.footnote": "<strong>Gathio</strong> {{version}} バージョン · <a href=\"https://github.com/lowercasename/gathio\">GitHub</a> · <a href=\"https://raphaelkabo.com\">Raphael</a> と <a href=\"https://github.com/lowercasename/gathio/graphs/contributors\">協力者たち</a> が <i class=\"far fa-heart\"></i> を込めて作成しています。", "mdsupport": "<a href='https://commonmark.org/help/'>Markdown</a> 書式対応", @@ -195,5 +205,6 @@ "snappy": "ズバッと。", "upcomingevents": "今後のイベント", "wontshow": "どこにも表示しません ( 任意 )。", + "year-month-format": "YYYY年MMM", "youremail": "あなたのメールアドレス" -} +}
\ No newline at end of file diff --git a/src/lib/event.ts b/src/lib/event.ts index 334ddf6..09631b9 100644 --- a/src/lib/event.ts +++ b/src/lib/event.ts @@ -1,3 +1,4 @@ +import i18next from "i18next"; import { IEventGroup } from "../models/EventGroup.js"; export interface EventListEvent { @@ -15,7 +16,8 @@ export const bucketEventsByMonth = ( acc: Record<string, any>[], event: EventListEvent, ) => { - const month = event.startMoment.format("MMMM YYYY"); + event.startMoment.locale(i18next.language); + const month = event.startMoment.format(i18next.t("year-month-format" )); const matchingBucket = acc.find((bucket) => bucket.title === month); if (!matchingBucket) { acc.push({ diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts index 40b5393..6f9e00a 100644 --- a/src/routes/frontend.ts +++ b/src/routes/frontend.ts @@ -71,7 +71,7 @@ router.get("/new/:magicLinkToken", async (req: Request, res: Response) => { ...frontendConfig(res), message: { type: "danger", - text: "This magic link is invalid or has expired. Please request a new one here.", + text: i18next.t("magiclink-invalid"), }, }); } @@ -92,6 +92,7 @@ router.get("/events", async (_: Request, res: Response) => { .lean() .sort("start"); const updatedEvents: EventListEvent[] = events.map((event) => { + moment.locale(i18next.language); const startMoment = moment.tz(event.start, event.timezone); const endMoment = moment.tz(event.end, event.timezone); const isSameDay = startMoment.isSame(endMoment, "day"); @@ -101,9 +102,9 @@ router.get("/events", async (_: Request, res: Response) => { name: event.name, location: event.location, displayDate: isSameDay - ? startMoment.format("D MMM YYYY") - : `${startMoment.format("D MMM YYYY")} - ${endMoment.format( - "D MMM YYYY", + ? startMoment.format("LL") + : `${startMoment.format("LL")} - ${endMoment.format( + "LL", )}`, eventHasConcluded: endMoment.isBefore(moment.tz(event.timezone)), eventGroup: event.eventGroup as any as IEventGroup, @@ -133,7 +134,7 @@ router.get("/events", async (_: Request, res: Response) => { }); res.render("publicEventList", { - title: "Public events", + title: i18next.t("frontend.publicevents"), upcomingEvents: upcomingEventsInMonthBuckets, pastEvents: pastEventsInMonthBuckets, eventGroups: updatedEventGroups, @@ -155,31 +156,57 @@ router.get("/:eventID", async (req: Request, res: Response) => { } const parsedLocation = event.location.replace(/\s+/g, "+"); let displayDate; + moment.locale(i18next.language); + const dateformat = i18next.t("frontend.dateformat"); + const timeformat = i18next.t('frontend.timeformat'); if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) { // Happening during one day - displayDate = - moment - .tz(event.start, event.timezone) - .format( - 'dddd D MMMM YYYY [<span class="text-muted">from</span>] h:mm a', - ) + - moment - .tz(event.end, event.timezone) - .format( - ' [<span class="text-muted">to</span>] h:mm a [<span class="text-muted">](z)[</span>]', - ); + displayDate = i18next.t("frontend.displaydate-sameday", + { + startdate: + moment + .tz(event.start, event.timezone) + .format(dateformat), + starttime: + moment + .tz(event.start, event.timezone) + .format(timeformat), + endtime: + moment + .tz(event.end, event.timezone) + .format(timeformat), + timezone: + i18next.t('frontend.sameday.timezone', + { tz: + moment + .tz(event.end, event.timezone) + .format('(z)',) + } ) + }); } else { - displayDate = - moment - .tz(event.start, event.timezone) - .format( - 'dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a', - ) + - moment - .tz(event.end, event.timezone) - .format( - ' [<span class="text-muted">–</span>] dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a [<span class="text-muted">](z)[</span>]', - ); + displayDate = i18next.t("frontend.displaydate-days", + { + startdate: + moment + .tz(event.start, event.timezone) + .format(dateformat), + starttime: + moment + .tz(event.start, event.timezone) + .format(timeformat), + enddate: + moment + .tz(event.end, event.timezone) + .format(dateformat), + endtime: + moment + .tz(event.end, event.timezone) + .format(timeformat), + timezone: + moment + .tz(event.end, event.timezone) + .format('(z)',) + }); } let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); @@ -430,8 +457,8 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => { .sort("start"); const updatedEvents: EventListEvent[] = events.map((event) => { - const startMoment = moment.tz(event.start, event.timezone); - const endMoment = moment.tz(event.end, event.timezone); + const startMoment = moment.tz(event.start, event.timezone).locale(i18next.language); + const endMoment = moment.tz(event.end, event.timezone).locale(i18next.language); const isSameDay = startMoment.isSame(endMoment, "day"); return { @@ -439,10 +466,8 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => { name: event.name, location: event.location, displayDate: isSameDay - ? startMoment.format("D MMM YYYY") - : `${startMoment.format("D MMM YYYY")} - ${endMoment.format( - "D MMM YYYY", - )}`, + ? startMoment.format("LL") + : `${startMoment.format("LL")} - ${endMoment.format("LL")}`, eventHasConcluded: endMoment.isBefore( moment.tz(event.timezone), ), diff --git a/views/event.handlebars b/views/event.handlebars index 009dfbd..1307983 100755 --- a/views/event.handlebars +++ b/views/event.handlebars @@ -285,9 +285,9 @@ <h5 class="card-header">{{t "event.discussion" }}</h5> <div class="card-body"> <form id="commentForm" action="/post/comment/{{eventData.id}}/" method="post"> - <label for="commentAuthor">{{t "event.attendeename" }}</label> + <label for="commentAuthor">{{t "event.commentauthor" }}</label> <div class="form-group"> - <input type="text" class="form-control" id="commentAuthor" name="commentAuthor" placeholder="{{t "event.attendeename" }}" required> + <input type="text" class="form-control" id="commentAuthor" name="commentAuthor" placeholder="{{t "event.commentauthorph" }}" required> </div> <label for="commentContent">{{t "event.comment" }}</label> <div class="form-group"> |