Skip to content

Commit aace1bb

Browse files
Merge pull request #18 from enricobattocchi/claude/fix-api-language-display-Vp7Rs
Add i18n support for date spans and event labels in OG image
2 parents d8189d1 + 3715eaf commit aace1bb

1 file changed

Lines changed: 45 additions & 4 deletions

File tree

src/app/api/og/route.tsx

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,45 @@ async function loadMessages(lang: string): Promise<Record<string, Record<string,
3838
}
3939
}
4040

41+
/** Resolve `{name}`-style placeholders. */
42+
function interpolate(template: string, values?: Record<string, string | number>): string {
43+
if (!values) return template;
44+
return template.replace(/\{(\w+)\}/g, (_, k) => (k in values ? String(values[k]) : `{${k}}`));
45+
}
46+
47+
/** Resolve a single ICU plural message like `{count, plural, one {# year} other {# years}}`. */
48+
function resolvePlural(template: string, locale: string, values?: Record<string, string | number>): string {
49+
const pluralMatch = template.match(/^\{(\w+),\s*plural,\s*([\s\S]+)\}$/);
50+
if (!pluralMatch) return interpolate(template, values);
51+
52+
const [, varName, body] = pluralMatch;
53+
const count = Number(values?.[varName] ?? 0);
54+
55+
// Parse branches like: one {# year} other {# years} =0 {no years}
56+
const branches = new Map<string, string>();
57+
const branchRegex = /(=?\w+)\s*\{([^{}]*)\}/g;
58+
let m: RegExpExecArray | null;
59+
while ((m = branchRegex.exec(body))) {
60+
branches.set(m[1], m[2]);
61+
}
62+
63+
let chosen = branches.get(`=${count}`);
64+
if (chosen === undefined) {
65+
const cat = new Intl.PluralRules(locale).select(count);
66+
chosen = branches.get(cat) ?? branches.get("other") ?? template;
67+
}
68+
69+
return interpolate(chosen.replace(/#/g, String(count)), values);
70+
}
71+
72+
function makeTranslator(messages: Record<string, Record<string, string>>, namespace: string, locale: string) {
73+
return (key: string, values?: Record<string, string | number>): string => {
74+
const template = messages[namespace]?.[key];
75+
if (!template) return key;
76+
return resolvePlural(template, locale, values);
77+
};
78+
}
79+
4180
export async function GET(request: NextRequest) {
4281
const { searchParams } = request.nextUrl;
4382
const ids = searchParams.get("ids");
@@ -77,7 +116,9 @@ export async function GET(request: NextRequest) {
77116
const font = await getFont();
78117

79118
const nowLabel = messages.common?.now || "Now";
80-
let timeline = allEvents.length > 0 ? computeTimeline(allEvents, 2, "en-US", nowLabel, undefined, hideNow) : null;
119+
const spanT = makeTranslator(messages, "date", lang);
120+
const eventLabelT = makeTranslator(messages, "eventLabel", lang);
121+
let timeline = allEvents.length > 0 ? computeTimeline(allEvents, 2, lang, nowLabel, spanT, hideNow) : null;
81122

82123
const fallbackDescription = messages.meta?.siteDescription || "Visualize the time between historical events.";
83124

@@ -197,9 +238,9 @@ export async function GET(request: NextRequest) {
197238
overflow: "visible",
198239
}}
199240
>
200-
{isNow ? nowLabel : eventDisplayName(marker.event).length > 25
201-
? eventDisplayName(marker.event).slice(0, 23) + "\u2026"
202-
: eventDisplayName(marker.event)}
241+
{isNow ? nowLabel : eventDisplayName(marker.event, eventLabelT).length > 25
242+
? eventDisplayName(marker.event, eventLabelT).slice(0, 23) + "\u2026"
243+
: eventDisplayName(marker.event, eventLabelT)}
203244
</div>
204245
</div>
205246
);

0 commit comments

Comments
 (0)