Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default defineConfig({
remarkPlugins: [remarkMath, remarkGfm],
rehypePlugins: [
rehypeSlug,
[rehypeMermaid, { strategy: "img-png" }],
[rehypeMermaid, { strategy: "pre-mermaid" }],
[
rehypeAutolinkHeadings,
{
Expand Down
28 changes: 19 additions & 9 deletions src/components/Footer/Subscribe/Subscribe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useState, useEffect } from "preact/hooks"
import MailchimpSubscribe from "react-mailchimp-subscribe"
import SubscribeSvg from "~/assets/svgs/footer/subscribe.svg?react"
import { clsx } from "~/lib"
import i18next, { changeLanguage, t } from "i18next"
import { t } from "i18next"
import { useI18nReady } from "~/hooks/useI18nReady"

import EmailInput from "./EmailInput.tsx"
import styles from "./Subscribe.module.css"
Expand All @@ -19,8 +20,7 @@ export default function Subscribe(props) {
const [email, setEmail] = useState("")
const [customMessage, setCustomMessage] = useState("")
const [emailValid, setEmailValid] = useState(false)

i18next.changeLanguage(props.lang)
const isReady = useI18nReady(props.lang)

useEffect(() => {
setCustomMessage("")
Expand All @@ -42,6 +42,10 @@ export default function Subscribe(props) {
setEmail(e.target.value)
}

if (!isReady) {
return null
}

return (
<div className={clsx(styles.container, "dark:bg-dark-highlight")}>
<div className={styles.subscribeBox}>
Expand All @@ -50,22 +54,28 @@ export default function Subscribe(props) {
</span>

<div className={styles.copyBox}>
<div className={styles.subscribeTitle}>{ t("landing.NewsletterCTA.title") }</div>
<div className={styles.subscribeText}>
{ t("landing.NewsletterCTA.text") }
</div>
<div className={styles.subscribeTitle}>{t("landing.NewsletterCTA.title")}</div>
<div className={styles.subscribeText}>{t("landing.NewsletterCTA.text")}</div>
</div>
<MailchimpSubscribe
url={url}
render={({ subscribe, status, message }: any) => (
render={({
subscribe,
status,
message,
}: {
subscribe: (data: { EMAIL: string }) => void
status: string
message: string
}) => (
<div className={styles.emailBox}>
<EmailInput
className={styles.emailInput}
value={email}
onChange={handleChangeEmail}
onClick={() => handleSubmit(subscribe)}
onEnter={() => handleSubmit(subscribe)}
placeholder= { t("landing.NewsletterCTA.placeholder") }
placeholder={t("landing.NewsletterCTA.placeholder")}
end={status === "success"}
/>
{customMessage && <div className={styles.errorMessage}>{customMessage}</div>}
Expand Down
6 changes: 6 additions & 0 deletions src/components/HeadCommon.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import "../styles/design-system/global-styles.css"
<!-- Scrollable a11y code helper -->
<script src="/make-scrollable-code-focusable.js" is:inline></script>

<!-- Mermaid.js for client-side rendering -->
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs"
mermaid.initialize({ startOnLoad: true, theme: "default" })
</script>

<!-- Google Tag Manager -->
<!-- <script type="text/javascript">
;(function (w, d, s, l, i) {
Expand Down
8 changes: 7 additions & 1 deletion src/components/RightSidebar/TableOfContents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useStore } from "@nanostores/preact"
import type { FunctionalComponent } from "preact"
import { useState, useEffect, useRef } from "preact/hooks"
import { shouldUpdateToc } from "./tocStore"
import i18next, { t } from "i18next"
import { t } from "i18next"
import { useI18nReady } from "~/hooks/useI18nReady"

export interface Heading {
depth: number
Expand All @@ -21,6 +22,7 @@ const TableOfContents: FunctionalComponent<{
const [currentID, setCurrentID] = useState("overview")
const onThisPageID = "on-this-page-heading"
const $shouldUpdateToc = useStore(shouldUpdateToc)
const isReady = useI18nReady()

useEffect(() => {
if (!tableOfContents.current) return
Expand Down Expand Up @@ -79,6 +81,10 @@ const TableOfContents: FunctionalComponent<{
setHeadings(headingList)
}

if (!isReady) {
return null
}

return (
<>
<h2 className="heading">{t("rightSidebar.onThisPage")}</h2>
Expand Down
69 changes: 69 additions & 0 deletions src/hooks/useI18nReady.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useState, useEffect } from "preact/hooks"
import i18next, { changeLanguage } from "i18next"

/**
* Custom hook to ensure i18next is initialized and translation resources are loaded
* before rendering components that use translations.
*
* This fixes the issue where Vercel build shows translation keys instead of translated text
* due to SSR/CSR timing differences.
*
* @param lang - Optional language code to change to. If provided, will change language and wait for resources.
* @returns boolean indicating if i18next is ready (initialized and resources loaded)
*/
export function useI18nReady(lang?: string): boolean {
const [isReady, setIsReady] = useState(false)

useEffect(() => {
const ensureResourcesLoaded = async () => {
try {
// Wait for i18next to be initialized
if (!i18next.isInitialized) {
await new Promise<void>((resolve) => {
const handler = () => {
i18next.off("initialized", handler)
resolve()
}
i18next.on("initialized", handler)
})
}

// Change language if needed (this will wait for resources to load)
if (lang && i18next.language !== lang) {
await changeLanguage(lang)
}

// Double-check that resources are actually loaded
const targetLang = lang || i18next.language || "en"
if (i18next.hasResourceBundle(targetLang, "translation")) {
setIsReady(true)
} else {
// Wait for resources to be loaded
await new Promise<void>((resolve) => {
const checkResources = () => {
if (i18next.hasResourceBundle(targetLang, "translation")) {
i18next.off("loaded", checkResources)
resolve()
}
}
i18next.on("loaded", checkResources)
// Check immediately in case resources are already loaded
if (i18next.hasResourceBundle(targetLang, "translation")) {
i18next.off("loaded", checkResources)
resolve()
}
})
setIsReady(true)
}
} catch (error) {
// If something goes wrong, still set ready to avoid blocking the UI
console.error("Error loading i18next resources:", error)
setIsReady(true)
}
}

ensureResourcesLoaded()
}, [lang])

return isReady
}
4 changes: 4 additions & 0 deletions src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,7 @@ h2.heading {
.mermaid-diagram > img {
height: auto;
}

.mermaid {
background: transparent;
}