Feature/post media translations (#42)

* chore: updated todo with translation ideas

* feat: first take at the implementation of translations

* fix: small addition for the translation feature

* feat: support language switching in the editor and preview

* feat: better handling of long bodies by not running them through a json envelope

* fix: unknown macros have better fallback

* feat: api for python to get translations

* fix: strip dumb prefix of content in translation

* feat: extend meta diff for translations

* feat: hook up translations to rebuild-from-disk

* feat: generation of the website prefers project language, falling back to canonical language

* fix: crashes during rendering

* feat: translation validation report

* fix: made the translation validation actually work

* chore: reorganization of menu

* fix: some topics cleanup

* chore: updated doc

* feat: translations for media

* feat: more aligned in UI/UX

* feat: edit translations possible

* chore: added full multi-language todo

* chore: updated todo for clarity

* feat: implementation of full multi-linguality

* fix: page creation creates pages

* fix: flags on every page

* fix: better prompt

* feat: made MCP server aware of language content

* feat: python tools for translations

* fix: better fill-in-translations

* fix: better prompt for translation. maybe.

* fix: losing posts from search due to translation process

* fix: translation validation handles in-db content and fill-in of missing translations fixed to flush

* fix: faster scanning for infilling of missing translations

* chore: updated agent instructions

* feat: calendar and tag cloud respect current language now

* fix: retries going up

* fix: got metadata-diff and rebuild into sync

* fix: extended meta-diff for timestamps

* fix: made website validation look at translated content, too

* fix: multi-lingual search

* chore: refactor Editor.tsx into two separate editors

* feat: do language detection when no explicit language given

---------

Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
Georg Bauer
2026-03-09 14:43:18 +01:00
committed by GitHub
parent f1c9038803
commit b855d61524
116 changed files with 19954 additions and 2094 deletions

View File

@@ -6,6 +6,7 @@ import { eq } from 'drizzle-orm';
import { getDatabase } from '../database';
import { posts, projects } from '../database/schema';
import { sanitizePicoTheme, type PicoThemeName } from '../shared/picoThemes';
import { SUPPORTED_RENDER_LANGUAGES, type SupportedLanguage } from '../shared/i18n';
import {
normalizeTaxonomyTerm,
normalizeNonEmptyTaxonomyTerm,
@@ -29,6 +30,7 @@ export interface ProjectMetadata {
categoryMetadata?: Record<string, CategoryMetadata>; // Per-category metadata for UI/rendering
categorySettings?: Record<string, CategoryRenderSettings>; // Per-category list rendering preferences
semanticSimilarityEnabled?: boolean; // Enable local ONNX embedding-based semantic similarity
blogLanguages?: string[]; // Languages the blog is rendered in (mainLanguage is always included)
}
export interface CategoryRenderSettings {
@@ -103,6 +105,19 @@ function sanitizeCategoryTitle(value: unknown, fallback: string): string {
type RawCategoryMetadataInput = Record<string, CategoryMetadata | CategoryRenderSettings>;
const supportedLanguageSet = new Set<string>(SUPPORTED_RENDER_LANGUAGES);
function sanitizeBlogLanguages(value: unknown): string[] | undefined {
if (!Array.isArray(value)) {
return undefined;
}
const filtered = value
.filter((item): item is string => typeof item === 'string')
.map((item) => item.trim().toLowerCase())
.filter((item) => item.length > 0 && supportedLanguageSet.has(item));
return filtered.length > 0 ? [...new Set(filtered)] : undefined;
}
function normalizeProjectMetadata(metadata: ProjectMetadata): ProjectMetadata {
const maxPostsPerPage = sanitizeMaxPostsPerPage(metadata.maxPostsPerPage);
const publicUrl = sanitizePublicUrl(metadata.publicUrl);
@@ -112,6 +127,7 @@ function normalizeProjectMetadata(metadata: ProjectMetadata): ProjectMetadata {
const pythonRuntimeMode = metadata.pythonRuntimeMode === 'main-thread' ? 'main-thread' : 'webworker';
const picoTheme = sanitizePicoTheme(metadata.picoTheme);
const categoryMetadata = normalizeCategoryMetadata(metadata.categoryMetadata ?? metadata.categorySettings);
const blogLanguages = sanitizeBlogLanguages(metadata.blogLanguages);
return {
...metadata,
publicUrl,
@@ -121,6 +137,7 @@ function normalizeProjectMetadata(metadata: ProjectMetadata): ProjectMetadata {
picoTheme,
categoryMetadata,
categorySettings: undefined,
blogLanguages,
};
}
@@ -349,6 +366,7 @@ export class MetaEngine extends EventEmitter {
picoTheme: normalizedUpdates.picoTheme,
categoryMetadata: normalizedUpdates.categoryMetadata,
semanticSimilarityEnabled: normalizedUpdates.semanticSimilarityEnabled,
blogLanguages: normalizedUpdates.blogLanguages,
});
} else {
this.projectMetadata = normalizeProjectMetadata({