i18n
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Failing after 10s
CI/CD Pipeline / Build and Push App Docker Image (push) Skipped
CI/CD Pipeline / Deploy to Kubernetes (push) Skipped

This commit is contained in:
2026-08-08 15:29:40 -07:00
parent 22c282d8fa
commit 0a7b5fa00f
178 changed files with 13099 additions and 1285 deletions
+45 -2
View File
@@ -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) {