feat: public API, SEO and existing surfaces

This commit is contained in:
Florian Sylvain committed 2026-11-03 03:58:10 +01:00
1 parent 45fbbc33b1
commit c390fcfe8d
103 files changed
+4476 -231

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import { locale, t } from '@/i18n'
import type { ResolvedSeo } from '@/lib/seoTypes'
import { languageName } from '@/lib/siteLanguages'
import { nameInSentence } from '@/lib/storyList'
export interface LocalizedSeoContext {
lang: string
siteName: string
siteKey: string
pattern: string | null
defaultLang: string
disabledLangs: string[]
published: boolean
socialState: string
resolved: ResolvedSeo | null
}
export function pageLanguage(context: LocalizedSeoContext) {
return languageName(context.lang, locale.value)
}
export function breadcrumbOf(url: string) {
try {
const parsed = new URL(url)
const segments = parsed.pathname.split('/').filter(Boolean)
return [parsed.host, ...segments].join(' › ')
} catch {
return url
}
}
export function canonicalNote(context: LocalizedSeoContext) {
if (context.resolved?.fallbacks.canonical === 'canonical_url') {
return t('Set by hand as a canonical override on this version.')
}
return t(
'Built from the Public URL pattern {0}. Each language points to itself unless you set a canonical override.',
[context.pattern ?? ''],
)
}
export function disabledNote(context: LocalizedSeoContext) {
if (context.disabledLangs.length === 0) {
return t('Drafts are left out.')
}
const names = context.disabledLangs.map((tag) => nameInSentence(tag))
const list = names.join(', ')
return names.length === 1
? t('Drafts and the disabled {0} language are left out.', [list])
: t('Drafts and the disabled languages {0} are left out.', [list])
}
export function socialLocaleLine(context: LocalizedSeoContext) {
const value = context.resolved?.og.locale ?? ''
if (!value) {
return t('Social locale not set')
}
return context.socialState === 'derived' ? t('Derived: {0}', [value]) : t('Set: {0}', [value])
}
export function socialLocaleNote(context: LocalizedSeoContext) {
const name = pageLanguage(context)
if (!context.resolved?.og.locale) {
return t(
'{0} has no region in its code and no social locale is set for it, so og:locale and og:locale:alternate are left out. An owner can set one in {1} languages.',
[name, context.siteName],
)
}
if (context.socialState === 'derived') {
return t(
'Taken from the region of the language code or of the site locale. An owner can set another in {0} languages.',
[context.siteName],
)
}
return t(
'Set by an owner for {0} in {1} languages. A region is never guessed from the language.',
[name, context.siteName],
)
}
export function titleHelp(context: LocalizedSeoContext) {
const source = context.resolved?.fallbacks.title
return source === 'title' ? t('Fallback: {0} title', [nameInSentence(context.lang)]) : ''
}
export function descriptionHelp(context: LocalizedSeoContext) {
const fallbacks = context.resolved?.fallbacks
if (!fallbacks) {
return ''
}
const name = nameInSentence(context.lang)
switch (fallbacks.description) {
case 'excerpt':
return t('Fallback: {0} excerpt', [name])
case 'body':
return t('Fallback: {0} body', [name])
case 'site':
return t('Fallback: {0} site description', [name])
case 'none':
return fallbacks.site_text_skipped
? t('No fallback. Empty. {0} site default not used. The tag is left out.', [
nameInSentence(context.defaultLang),
])
: t('No fallback')
default:
return ''
}
}
export function descriptionBadge(context: LocalizedSeoContext) {
const fallbacks = context.resolved?.fallbacks
if (fallbacks?.description === 'site') {
return t('Uses site text')
}
return fallbacks?.site_text_skipped ? t('Tag left out') : ''
}
export function altHelp() {
return t('Written per language.')
}