Localization (#1)
Implement comprehensive content localization, admin panels for editing each language, AI translation interface with automatic updates when base language version is changed. - SEO tags for all language URLs - Uses accept-language by default, ?lang=en overrides temporarily - User edits patched on top of translations - RTL language supportReviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// Shared reconnect policy for the WebSockets (page/banner editors,
|
||||
// analytics view, the activity channel). Two things trip a browser's
|
||||
// WebSocket throttling, after which every socket to the host sits
|
||||
// "pending" (never opens, never closes) for minutes:
|
||||
//
|
||||
// 1. A burst of simultaneous attempts — page load opens Vite's HMR
|
||||
// socket plus several of ours at the same moment, and every refresh
|
||||
// repeats the burst. socketSlot() spaces new sockets out.
|
||||
// 2. Too-frequent retries — so failed attempts back off exponentially
|
||||
// (a few seconds, doubling to half a minute), reset only after a
|
||||
// connection stayed open long enough to count as healthy. A socket
|
||||
// that closes right after opening must NOT reset the backoff.
|
||||
export function reconnectPolicy({ min = 2000, max = 30000, healthyAfter = 30000 } = {}) {
|
||||
let delay = min
|
||||
let openedAt = 0
|
||||
return {
|
||||
// Stamp a socket that just opened.
|
||||
opened() {
|
||||
openedAt = Date.now()
|
||||
},
|
||||
// The socket closed: the wait before the next attempt (up to 50%
|
||||
// jitter; the base doubles per failure). A healthy streak resets it.
|
||||
closed() {
|
||||
if (openedAt && Date.now() - openedAt >= healthyAfter) delay = min
|
||||
openedAt = 0
|
||||
const wait = Math.round(delay * (1 + Math.random() * 0.5))
|
||||
delay = Math.min(delay * 2, max)
|
||||
return wait
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Sockets created at the same moment (page load: Vite's HMR socket plus
|
||||
// ours) read as one burst to the browser's throttling. Space new sockets
|
||||
// out: each call reserves a slot a beat after the previous one.
|
||||
let nextSlot = 0
|
||||
export function socketSlot() {
|
||||
const now = Date.now()
|
||||
const wait = Math.max(0, nextSlot - now)
|
||||
nextSlot = Math.max(now, nextSlot) + 300
|
||||
return wait
|
||||
}
|
||||
|
||||
// A socket still CONNECTING after this long counts as a failed attempt:
|
||||
// browser throttling leaves sockets "pending" (no open, no close) for
|
||||
// minutes, and without a watchdog the app would wait on one forever (the
|
||||
// recurring empty editor). Closing it fires onclose, which reschedules
|
||||
// through the policy's backoff — it never reconnects aggressively itself.
|
||||
export function watchConnecting(ws, label) {
|
||||
return setTimeout(() => {
|
||||
if (ws.readyState === WebSocket.CONNECTING) {
|
||||
console.warn(`[pagerite] ${label} socket stuck connecting — closing it, retrying with backoff`)
|
||||
ws.close()
|
||||
}
|
||||
}, 10_000)
|
||||
}
|
||||
Reference in New Issue
Block a user