Make the resume bilingual and part of the shared site chrome
Adds a Russian version (data/resume.ru.json, default language) with an RU/EN switch stored under the key shared with the front page and the portfolio. The top bar becomes the site-wide one (brand, Resume, Portfolio, Gitea, Contact, language, theme) with a second row of anchors for this page's sections, and the fonts move to Unbounded and Onest so the three pages share one type system. Portfolio links now point at /portfolio/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtcZtnG4GQA1d6mW5PNrS9
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export type Lang = 'ru' | 'en'
|
||||
|
||||
/** Both languages side by side; html[data-lang] decides which one is shown, so there is no flash and no state. */
|
||||
export function Bi({ ru, en }: Readonly<{ ru: ReactNode; en: ReactNode }>) {
|
||||
return (
|
||||
<>
|
||||
<span data-ru>{ru}</span>
|
||||
<span data-en>{en}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const pluralRu = new Intl.PluralRules('ru')
|
||||
const pluralEn = new Intl.PluralRules('en')
|
||||
|
||||
/** "21 проект" / "21 project": Russian has three forms (1 / 2-4 / 5+), English two. */
|
||||
export function count(n: number, ru: [string, string, string], en: [string, string]) {
|
||||
const r = pluralRu.select(n)
|
||||
return {
|
||||
ru: `${n} ${r === 'one' ? ru[0] : r === 'few' ? ru[1] : ru[2]}`,
|
||||
en: `${n} ${pluralEn.select(n) === 'one' ? en[0] : en[1]}`,
|
||||
}
|
||||
}
|
||||
|
||||
export const monthYear = (iso: string | undefined, lang: Lang) =>
|
||||
iso ? new Date(iso).toLocaleDateString(lang, { month: 'short', year: 'numeric' }) : ''
|
||||
|
||||
// Where the other pages of shanaty.ru live. Empty in the production build (root-relative links); the default is
|
||||
// for builds that run elsewhere (dev, GitHub Pages), where relative links would point at the wrong site.
|
||||
export const SITE = process.env.NEXT_PUBLIC_SITE_ORIGIN ?? 'https://shanaty.ru'
|
||||
+1
-1
@@ -44,4 +44,4 @@ export type Project = {
|
||||
export type Portfolio = { href: string; count?: number; label?: string }
|
||||
|
||||
// Shape of /data/resume.json, written every minute by the server-side sync.
|
||||
export type Live = { generated_at: string; resume: Resume; portfolio?: Portfolio; projects: Project[] }
|
||||
export type Live = { generated_at: string; resume: Resume; resume_ru?: Resume; portfolio?: Portfolio; stats?: { repos?: number }; projects: Project[] }
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Lang } from './i18n'
|
||||
|
||||
/** The current language (html[data-lang], set before the first paint); re-reads when the switch is used. */
|
||||
export function useLang(): Lang {
|
||||
const [lang, setLang] = useState<Lang>('ru')
|
||||
useEffect(() => {
|
||||
const read = () => {
|
||||
const html = document.documentElement
|
||||
const current: Lang = html.dataset.lang === 'en' ? 'en' : 'ru'
|
||||
setLang(current)
|
||||
}
|
||||
read()
|
||||
window.addEventListener('langchange', read)
|
||||
return () => window.removeEventListener('langchange', read)
|
||||
}, [])
|
||||
return lang
|
||||
}
|
||||
Reference in New Issue
Block a user