i18n
This commit is contained in:
+45
-2
@@ -1,7 +1,10 @@
|
||||
import webpush from "web-push";
|
||||
import UserMetadata from "supertokens-node/recipe/usermetadata";
|
||||
import type { I18n, MessageDescriptor } from "@lingui/core";
|
||||
import { vapidConfig, isPushConfigured } from "@/lib/config";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { createI18n, resolveLocale } from "@/lib/i18n";
|
||||
import type { PushSubscriptionRecord } from "@/lib/pocketbase/services/push";
|
||||
|
||||
export interface PushPayload {
|
||||
@@ -15,6 +18,35 @@ export interface PushPayload {
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
/** A literal string (e.g. the brand name, never translated) or a localizable message descriptor. */
|
||||
export type PushText = string | MessageDescriptor;
|
||||
|
||||
export interface LocalizedPushPayload extends Omit<PushPayload, "title" | "body"> {
|
||||
title: PushText;
|
||||
body: PushText;
|
||||
}
|
||||
|
||||
function resolvePushText(i18n: I18n, text: PushText): string {
|
||||
return typeof text === "string" ? text : i18n._(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* sendPushToPlayer only has the recipient's playerId, not a request context,
|
||||
* so it resolves their locale from SuperTokens user metadata directly.
|
||||
* Falls back to the default locale for players with no linked auth account
|
||||
* or no stored locale preference.
|
||||
*/
|
||||
async function resolveRecipientLocale(playerId: string) {
|
||||
try {
|
||||
const player = await pbAdmin.getPlayer(playerId);
|
||||
if (!player.auth_id) return resolveLocale(undefined);
|
||||
const { metadata } = await UserMetadata.getUserMetadata(player.auth_id);
|
||||
return resolveLocale(metadata?.locale);
|
||||
} catch {
|
||||
return resolveLocale(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
let vapidInitialized = false;
|
||||
|
||||
function ensureVapid(): boolean {
|
||||
@@ -61,13 +93,24 @@ export async function sendPush(
|
||||
* - a badge is awarded to the player (src/lib/pocketbase/services/badges.ts)
|
||||
* NONE of these are wired yet — this foundation pass only exposes the seam
|
||||
* plus a manual "test notification" path.
|
||||
*
|
||||
* title/body accept either a literal string (e.g. the brand name) or a
|
||||
* msg`` descriptor, resolved against the recipient's own locale.
|
||||
*/
|
||||
export async function sendPushToPlayer(
|
||||
playerId: string,
|
||||
payload: PushPayload
|
||||
payload: LocalizedPushPayload
|
||||
): Promise<{ sent: number; failed: number; pruned: number }> {
|
||||
if (!ensureVapid()) return { sent: 0, failed: 0, pruned: 0 };
|
||||
|
||||
const locale = await resolveRecipientLocale(playerId);
|
||||
const i18n = createI18n(locale);
|
||||
const resolvedPayload: PushPayload = {
|
||||
...payload,
|
||||
title: resolvePushText(i18n, payload.title),
|
||||
body: resolvePushText(i18n, payload.body),
|
||||
};
|
||||
|
||||
const subscriptions: PushSubscriptionRecord[] =
|
||||
await pbAdmin.listPushSubscriptionsByPlayer(playerId);
|
||||
|
||||
@@ -83,7 +126,7 @@ export async function sendPushToPlayer(
|
||||
endpoint: sub.endpoint,
|
||||
keys: { p256dh: sub.p256dh, auth: sub.auth },
|
||||
},
|
||||
payload
|
||||
resolvedPayload
|
||||
);
|
||||
sent++;
|
||||
} catch (error: any) {
|
||||
|
||||
Reference in New Issue
Block a user