diff options
| author | INOUE Daisuke <inoue.daisuke@gmail.com> | 2025-04-08 22:18:39 +0900 | 
|---|---|---|
| committer | INOUE Daisuke <inoue.daisuke@gmail.com> | 2025-04-08 22:18:39 +0900 | 
| commit | 1b57d9ea6513b81e538677f9ebf221d0c635f482 (patch) | |
| tree | 14b748f14f2d123c9dc1e9ae73978e6d618839b8 | |
| parent | 90bdf104d76674d307cbd50dc1cf3d973b663471 (diff) | |
Plural with i18next
| -rw-r--r-- | locales/en.json | 7 | ||||
| -rw-r--r-- | locales/ja.json | 7 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 11 | ||||
| -rwxr-xr-x | src/app.ts | 32 | ||||
| -rwxr-xr-x | views/event.handlebars | 4 | 
6 files changed, 40 insertions, 22 deletions
diff --git a/locales/en.json b/locales/en.json index 118f139..afa6d5f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -42,6 +42,9 @@    "event.ended": "Ended",    "event.enternum": "Enter a number.",    "event.hidden": "(hidden from public list)", +  "event.hiddenattendee_one": "{{count}} hidden attendee", +  "event.hiddenattendee_other": "{{count}} hidden attendees", +  "event.hiddenattendee_zero": "No hidden attendee",    "event.hostedby": "Hosted by</span> {{eventData.hostName}}",    "event.ICSexport": "Export as ICS",    "event.locationdesc": "Be specific.", @@ -75,7 +78,9 @@    "event.p.timezone": "Timezone",    "event.partof": "<a href='/group/{{eventData.eventGroup.id}}'>{{eventData.eventGroup.name}}</a>",    "event.postbutton": "Post comment", -  "event.remaining": "{{spotsRemaining}} {{plural spotsRemaining \"spot(s)\"}} remaining - add yourself now!", +  "event.remaining_one": "{{count}} spot remaining - add yourself now!", +  "event.remaining_other": "{{count}} spots remaining - add yourself now!", +  "event.remaining_zero": "This event is at capacity.",    "event.remove-attendee": "Remove {{ attendeeName }} from {{eventData.name}}",    "event.removeAttendee": "Remove attendee",    "event.removeattendeedesc": "Remove attendee from '{{eventData.name}}'", diff --git a/locales/ja.json b/locales/ja.json index a0ae374..f04c342 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -42,6 +42,9 @@    "event.ended": "終了済み",    "event.enternum": "人数を入力してください",    "event.hidden": "( 匿名 )", +  "event.hiddenattendee_one": "匿名 {{count}} 人", +  "event.hiddenattendee_other": "匿名 {{count}} 人", +  "event.hiddenattendee_zero": "匿名 なし",    "event.hostedby": "主催 : </span> {{eventData.hostName}}",    "event.ICSexport": "iCalendar ファイル出力",    "event.locationdesc": "具体的に。", @@ -75,7 +78,9 @@    "event.p.timezone": "タイムゾーン",    "event.partof": "<a href='/group/{{eventData.eventGroup.id}}'>{{eventData.eventGroup.name}}</a> グループのイベント",    "event.postbutton": "送信", -  "event.remaining": "残り {{spotsRemaining}} 枠 - 参加登録しましょう !", +  "event.remaining_one": "残り {{count}} 枠 - 参加登録しましょう !", +  "event.remaining_other": "残り {{count}} 枠 - 参加登録しましょう !", +  "event.remaining_zero": "このイベントは満員です。",    "event.remove-attendee": "'{{eventData.name}}' から {{ attendeeName }} を削除",    "event.removeAttendee": "参加者を削除",    "event.removeattendeedesc": "'{{eventData.name}}' から参加者を削除", diff --git a/package.json b/package.json index e297d10..0182815 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@          "@types/cookie-parser": "^1.4.7",          "@types/dompurify": "^3.0.5",          "@types/express": "^4.17.21", +        "@types/handlebars": "^4.1.0",          "@types/i18next-fs-backend": "^1.2.0",          "@types/ical": "^0.8.3",          "@types/jsdom": "^21.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c486f9..c45ff07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers:        '@types/express':          specifier: ^4.17.21          version: 4.17.21 +      '@types/handlebars': +        specifier: ^4.1.0 +        version: 4.1.0        '@types/i18next-fs-backend':          specifier: ^1.2.0          version: 1.2.0 @@ -445,6 +448,10 @@ packages:    '@types/express@4.17.21':      resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} +  '@types/handlebars@4.1.0': +    resolution: {integrity: sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA==} +    deprecated: This is a stub types definition. handlebars provides its own type definitions, so you do not need this installed. +    '@types/http-errors@2.0.4':      resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -2825,6 +2832,10 @@ snapshots:        '@types/qs': 6.9.15        '@types/serve-static': 1.15.7 +  '@types/handlebars@4.1.0': +    dependencies: +      handlebars: 4.7.8 +    '@types/http-errors@2.0.4': {}    '@types/i18next-fs-backend@1.2.0': @@ -1,5 +1,6 @@  import express from "express"; -import hbs from "express-handlebars"; +import hbs, { ExpressHandlebars } from "express-handlebars"; +import Handlebars from 'handlebars';  import cookieParser from "cookie-parser";  import i18next from "i18next";  import Backend from "i18next-fs-backend"; @@ -98,31 +99,20 @@ async function initializeApp() {      });      // View engine // -    const hbsInstance = hbs.create({ +    const hbsInstance: ExpressHandlebars = hbs.create({          defaultLayout: "main",          partialsDir: ["views/partials/"],          layoutsDir: "views/layouts/",          helpers: { -            plural: function (number: number, text: string) { -                var singular = number === 1; -                // If no text parameter was given, just return a conditional s. -                if (typeof text !== "string") return singular ? "" : "s"; -                // Split with regex into group1/group2 or group1(group3) -                var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); -                // If no match, just append a conditional s. -                if (!match) return text + (singular ? "" : "s"); -                // We have a good match, so fire away -                return ( -                    (singular && match[1]) || // Singular case -                    match[2] || // Plural case: 'bagel/bagels' --> bagels -                    match[1] + (match[3] || "s") -                ); // Plural case: 'bagel(s)' or 'bagel' --> bagels -            },              json: function (context: any) {                  return JSON.stringify(context);              },              // i18nextヘルパーを追加 -            ...getI18nHelpers() +            ...getI18nHelpers(), +            plural: function (key: string, count: number, options: any) { // ★plural ヘルパーを登録 +                const translation = i18next.t(key, { count: count }); +                return translation; +            }          },      }); @@ -135,6 +125,12 @@ async function initializeApp() {          console.error('handlebars-i18next helper is not properly loaded');      } + +    (hbsInstance.handlebars as typeof Handlebars).registerHelper('pluralize', function(count: number, key: string, options: any) { +        const translation = i18next.t(key, { count: count }); +        return translation; +    }); +      app.engine("handlebars", hbsInstance.engine);      app.set("view engine", "handlebars");      app.set("hbsInstance", hbsInstance); diff --git a/views/event.handlebars b/views/event.handlebars index c36ab30..009dfbd 100755 --- a/views/event.handlebars +++ b/views/event.handlebars @@ -149,7 +149,7 @@        {{#if noMoreSpots}}          <div class="alert alert-warning text-center" id="attendees-alert">{{t "event.capacity" }}</div>        {{else}} -        <div class="alert alert-warning text-center" id="attendees-alert">{{t "event.remaining" }}</div> +        <div class="alert alert-warning text-center" id="attendees-alert">{{plural "event.remaining" spotsRemaining }}</div>        {{/if}}      {{/if}}      {{#if numberOfAttendees}} @@ -165,7 +165,7 @@        </ul>        {{#unless editingEnabled}}          {{#if numberOfHiddenAttendees}} -          <div class="hidden-attendees-message">{{numberOfHiddenAttendees}} hidden attendee{{plural numberOfHiddenAttendees ""}}</div> +          <div class="hidden-attendees-message">{{plural "event.hiddenattendee" numberOfHiddenAttendees }}</div>          {{/if}}        {{/unless}}      {{else}}  | 
