Make the resume bilingual and part of the shared site chrome
Deploy to GitHub Pages / build (push) Canceled after 0s
Deploy to GitHub Pages / deploy (push) Canceled after 0s

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:
Emil
2026-09-20 23:48:38 +03:00
co-authored by Claude Sonnet 5
parent 4d485fc19d
commit 04c1cc5877
27 changed files with 425 additions and 176 deletions
+32
View File
@@ -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
View File
@@ -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[] }
+20
View File
@@ -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
}