From 14a3e4d1ea06e03dc18b566efdc413607a1d5143 Mon Sep 17 00:00:00 2001 From: Anthony Bailey Date: Thu, 8 May 2025 00:24:43 +0100 Subject: [PATCH 1/6] fix: routing with urls and prefix-all-locales/patterns to treat en the same We want non-localized routes to be handled dynamically via redirects, and all content localized to xx to be static under the URL xx/some-page. We expect this to work best with Netlify, and be least confusing. The Paraglide 2.0 framework needs both prefix-all-locales as a routing strategy and also for us to specify all patterns so as to force /en/ as a prefix - the default patterns don't truly honor prefix-all-locales. Implementation in writeSettingsFile() since it has the final locale list. Since visiting a URL now sets the locale cookie, also added a visual effect to the locale switcher button so folk can see when this has occurred. Also includes a bugfix: recent LLM message translations had added commentary around the JSON object, which meant Paraglide couldn't parse those messages. I've implemented protection against this at last responsible moment (before compiling) but can move it earlier when we work more on LLM improvements. --- package.json | 2 + pnpm-lock.yaml | 17 ++++ project.inlang/default-settings.js | 4 +- scripts/inlang-settings.ts | 39 +++++----- scripts/translation/utils.ts | 26 +++++++ src/lib/components/LanguageSwitcher.svelte | 91 ++++++++++++++-------- src/lib/components/Navlink.svelte | 2 +- src/routes/header.svelte | 6 +- src/routes/sitemap.txt/+server.ts | 2 +- src/routes/sitemap.xml/+server.ts | 4 +- 10 files changed, 132 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 1bb4a919b..785589d7d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/escape-html": "^1.0.4", "@types/glidejs__glide": "^3.6.6", + "@types/js-cookie": "^3.0.6", "@types/minimist": "^1.2.5", "@types/node": "^20.17.31", "@types/remark-heading-id": "^1.0.0", @@ -84,6 +85,7 @@ "escape-html": "^1.0.3", "github-slugger": "^2.0.0", "html-to-image": "^1.11.13", + "js-cookie": "^3.0.5", "lucide-svelte": "^0.325.0", "maplibre-gl": "^5.4.0", "maplibregl-mapbox-request-transformer": "^0.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c922ec332..eeeae3b30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: html-to-image: specifier: ^1.11.13 version: 1.11.13 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-svelte: specifier: ^0.325.0 version: 0.325.0(svelte@4.2.19) @@ -117,6 +120,9 @@ importers: '@types/glidejs__glide': specifier: ^3.6.6 version: 3.6.6 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/minimist': specifier: ^1.2.5 version: 1.2.5 @@ -960,6 +966,9 @@ packages: '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1804,6 +1813,10 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-sha256@0.11.0: resolution: {integrity: sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==} @@ -3323,6 +3336,8 @@ snapshots: dependencies: '@types/unist': 2.0.11 + '@types/js-cookie@3.0.6': {} + '@types/json-schema@7.0.15': {} '@types/mapbox-gl@2.7.21': @@ -4202,6 +4217,8 @@ snapshots: isexe@3.1.1: {} + js-cookie@3.0.5: {} + js-sha256@0.11.0: {} js-yaml@4.1.0: diff --git a/project.inlang/default-settings.js b/project.inlang/default-settings.js index 78f60682b..8a1173fcc 100644 --- a/project.inlang/default-settings.js +++ b/project.inlang/default-settings.js @@ -13,11 +13,11 @@ export default { 'https://cdn.jsdelivr.net/npm/@inlang/plugin-paraglide-js-adapter@latest/dist/index.js' ], 'plugin.inlang.messageFormat': { - pathPattern: './messages/{locale}.json' + pathPattern: './src/temp/translations/json/{locale}.json' }, 'plugin.paraglide-js-adapter': { routing: { - strategy: 'prefix', + strategy: 'prefix-all-locales', defaultLocale: 'en' } } diff --git a/scripts/inlang-settings.ts b/scripts/inlang-settings.ts index 8fb4dcfaa..4bd0cf749 100644 --- a/scripts/inlang-settings.ts +++ b/scripts/inlang-settings.ts @@ -12,26 +12,11 @@ import { writeSettingsFile } from '../src/lib/l10n' import { setupTranslationRepo } from './translation/git-ops' -import { createSymlinkIfNeeded, ensureDirectoriesExist } from './translation/utils' +import { cullCommentary, createSymlinkIfNeeded, ensureDirectoriesExist } from './translation/utils' // Load environment variables from .env file dotenv.config() -// Configuration - same as in vite.config.ts -const PROJECT_PATH = './project.inlang' -const OUTPUT_PATH = './src/lib/paraglide' -const COMPILE_ARGS = { - project: PROJECT_PATH, - outdir: OUTPUT_PATH, - strategy: ['cookie', 'url', 'preferredLanguage', 'baseLocale'] as ( - | 'cookie' - | 'url' - | 'preferredLanguage' - | 'baseLocale' - | 'globalVariable' - | 'localStorage' - )[] -} function setupEnglishSupport(verbose: boolean): void { // English markdown files are loaded directly from source in routes/[slug]/+page.ts if (verbose) console.log(' \u2713 English markdown files will be loaded directly from source') @@ -93,6 +78,11 @@ function regenerateSettings(verbose = false): void { `\n\ud83d\udd04 Setting up translation repository (need at least ${settings.locales}...` ) setupTranslationRepo(L10NS_BASE_DIR, verbose) + if (verbose) console.log(`\n🧹 Cleaning up translation files to remove LLM commentary...`) + for (const locale of settings.locales) { + if (locale === 'en') continue + cullCommentary(path.join(MESSAGE_L10NS, `${locale}.json`), verbose) + } } // For English locale, we only need to provide messages file for Paraglide @@ -118,8 +108,21 @@ function regenerateSettings(verbose = false): void { console.log(`\ud83d\udd04 Compiling Paraglide runtime from settings...`) try { - // Run the Paraglide compiler with the necessary Node.js flags - compile(COMPILE_ARGS) + compile({ + project: './project.inlang', + outdir: './src/lib/paraglide', + strategy: ['url', 'cookie', 'preferredLanguage', 'baseLocale'], + // Create concrete URL patterns structure with current locale set + urlPatterns: [ + { + pattern: ':protocol://:domain(.*)::port?/:path(.*)?', + localized: settings.locales.map((locale) => [ + locale, + `:protocol://:domain(.*)::port?/${locale}/:path(.*)?` + ]) + } + ] + }) console.log(`\u2705 Paraglide runtime compiled successfully!`) } catch (error) { console.error('\u274c Failed to compile Paraglide runtime:', (error as Error).message) diff --git a/scripts/translation/utils.ts b/scripts/translation/utils.ts index 795ca5e52..c20917eda 100644 --- a/scripts/translation/utils.ts +++ b/scripts/translation/utils.ts @@ -229,3 +229,29 @@ export async function writeFileWithDir(filePath: string, content: string): Promi await fs.mkdir(dir, { recursive: true }) fsSync.writeFileSync(filePath, content) } + +/** + * Cleans up potential LLM commentary in translation JSON files + * Strips anything before the first '{' and after the last '}' + * + * @param filePath - Path to the JSON file to clean + * @param verbose - Whether to output verbose logs + */ +export function cullCommentary(filePath: string, verbose = false): boolean { + try { + const content = fsSync.readFileSync(filePath, 'utf-8') + const firstBrace = content.indexOf('{') + const lastBrace = content.lastIndexOf('}') + + if (firstBrace === -1 || lastBrace === -1) throw new Error('No JSON object found in file') + + const jsonContent = content.substring(firstBrace, lastBrace + 1) + JSON.parse(jsonContent) // checks validity + if (jsonContent === content) return + fsSync.writeFileSync(filePath, jsonContent, 'utf-8') + + if (verbose) console.log(`āœ… Culled LLM commentary in ${filePath}`) + } catch (error) { + console.error(`Error cleaning up file ${filePath}:`, error.message) + } +} diff --git a/src/lib/components/LanguageSwitcher.svelte b/src/lib/components/LanguageSwitcher.svelte index df54f5609..9e8ab420a 100644 --- a/src/lib/components/LanguageSwitcher.svelte +++ b/src/lib/components/LanguageSwitcher.svelte @@ -13,12 +13,16 @@ import { building } from '$app/environment' import { onMount } from 'svelte' import Card from '$lib/components/Card.svelte' + import Cookies from 'js-cookie' export let inverted = false // Check if we should show the language switcher (only show when multiple locales) const showSwitcher = locales.length > 1 + // Flag to track if locale was changed externally + let externalLocaleChange = false + // Display name utilities for multiple locales const languageNamesInEnglish = new Intl.DisplayNames('en', { type: 'language' }) // Keep a map of locale-specific display names @@ -64,31 +68,35 @@ return `${currentLocaleName} (${nativeName})` } - // Display the current locale state in UI and console for debugging - // TODO: disable this once everything is confirmed to work well in production - function debugLocaleState() { - // Get current values - const currentLocale = getLocale() - const cookieLocale = document.cookie - .split('; ') - .find((row) => row.startsWith('PARAGLIDE_LOCALE=')) - ?.split('=')[1] - const pathLocale = window.location.pathname.split('/')[1] - const isPathLocale = locales.includes(pathLocale as any) - - console.log('šŸ” Locale Debug:', { - getLocale: currentLocale, - cookieLocale, - pathLocale: isPathLocale ? pathLocale : null, - allLocales: locales - }) - - return currentLocale + // Check if cookie was changed externally (and update shadow cookie which tracks this) + function checkLocaleChange(): boolean { + if (typeof window === 'undefined') return false + + const cookieLocale = Cookies.get('PARAGLIDE_LOCALE') + const shadowLocale = Cookies.get('PARAGLIDE_LOCALE_SHADOW') + + // Check for change - treat undefined shadow cookie as a change too + // (This catches first-time visits where shadow cookie isn't set yet) + const wasChanged = shadowLocale !== cookieLocale + + // Always update the shadow cookie to match the current value + if (cookieLocale) { + Cookies.set('PARAGLIDE_LOCALE_SHADOW', cookieLocale, { path: '/' }) + } else { + Cookies.remove('PARAGLIDE_LOCALE_SHADOW', { path: '/' }) + } + + return wasChanged } onMount(() => { - // Debug on mount - debugLocaleState() + if (typeof window === 'undefined') return + + if (checkLocaleChange()) { + // Add visual indication + console.log('🌐 Locale was changed externally!') + if (button) button.classList.add('locale-changed') + } // Only set up event listeners if language switcher is visible if (showSwitcher) { @@ -111,16 +119,6 @@ const href = target.href const targetLocale = target.getAttribute('hreflang') as Locale | 'auto' - // Debug current state before change - console.log('šŸ” Before click:') - const currentLocale = debugLocaleState() - - console.log('Language click:', { - from: currentLocale, - to: targetLocale, - href: href - }) - // Close the dropdown open = false @@ -128,14 +126,17 @@ event.preventDefault() if (targetLocale === 'auto') { - document.cookie = 'PARAGLIDE_LOCALE=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' - document.cookie = 'PARAGLIDE_LOCALE=; path=/; max-age=0' + // Remove both cookies for auto-detect + Cookies.remove('PARAGLIDE_LOCALE', { path: '/' }) + Cookies.remove('PARAGLIDE_LOCALE_SHADOW', { path: '/' }) window.location.href = deLocalizeHref(window.location.pathname) return } // Handle regular locale selection if (targetLocale) { + // Update shadow cookie to prevent detection as an external change + Cookies.set('PARAGLIDE_LOCALE_SHADOW', targetLocale, { path: '/' }) // explicit reload seems necessary to interact correctly with SvelteKit client-side refresh setLocale(targetLocale, { reload: true }) // As a fallback, manually reload if we're still here after a short delay @@ -155,6 +156,7 @@ bind:this={button} on:click={(e) => { e.preventDefault() + button.classList.remove('locale-changed') // user has attended open = !open }} > @@ -254,4 +256,25 @@ visibility: hidden; pointer-events: none; } + + /* Visual indicator for locale change */ + :global(.locale-changed) { + animation: pulse 2s infinite; + transform-origin: center; + } + + @keyframes pulse { + 0% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.6; + transform: scale(1.2); + } + 100% { + opacity: 1; + transform: scale(1); + } + } diff --git a/src/lib/components/Navlink.svelte b/src/lib/components/Navlink.svelte index bc5e2ab91..a8e619548 100644 --- a/src/lib/components/Navlink.svelte +++ b/src/lib/components/Navlink.svelte @@ -13,7 +13,7 @@ $: localizedHref = href && !external ? localizeHref(href) : href $: { - active = localizeUrl($page.url.pathname) == localizedHref + active = localizeHref($page.url.pathname) == localizedHref } diff --git a/src/routes/header.svelte b/src/routes/header.svelte index 4ec62a8fb..d64443637 100644 --- a/src/routes/header.svelte +++ b/src/routes/header.svelte @@ -6,7 +6,7 @@ import SearchIcon from 'lucide-svelte/icons/search' import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte' import * as m from '$lib/paraglide/messages.js' - import { localizeUrl } from '$lib/paraglide/runtime' + import { localizeHref } from '$lib/paraglide/runtime' import { initializeCqwResizeObserver } from '$lib/container-query-units' import { onMount } from 'svelte' @@ -15,7 +15,7 @@ export let inverted = false export let moveUp = false - $: logo_animate = localizeUrl($page.url.pathname) != '/' + $: logo_animate = $page.url.pathname != localizeHref('/') let nav: HTMLElement @@ -30,7 +30,7 @@
-
diff --git a/src/routes/sitemap.txt/+server.ts b/src/routes/sitemap.txt/+server.ts index 6893856c2..d19fa75e7 100644 --- a/src/routes/sitemap.txt/+server.ts +++ b/src/routes/sitemap.txt/+server.ts @@ -11,7 +11,7 @@ export async function GET({ fetch }) { const headers = { 'Content-Type': 'text/plain' } const sitemap = posts - .map(({ slug }) => `${website}/${slug}\n`) + .map(({ slug }) => `${website}/en/${slug}\n`) .join('') .trim() diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts index 5bbce2553..f3e8bb1b9 100644 --- a/src/routes/sitemap.xml/+server.ts +++ b/src/routes/sitemap.xml/+server.ts @@ -21,7 +21,7 @@ export async function GET({ fetch }) { xmlns:video="https://www.google.com/schemas/sitemap-video/1.1" > - ${website} + ${website}/en daily 0.7 @@ -29,7 +29,7 @@ export async function GET({ fetch }) { .map( (post) => ` - ${website}/${post.slug} + ${website}/en/${post.slug} daily 0.7 ` From f9fca656687c21ebe8fb739fa65e7d2df4364ee4 Mon Sep 17 00:00:00 2001 From: Anthony Bailey Date: Tue, 20 May 2025 06:11:55 +0100 Subject: [PATCH 2/6] feat: Improve localization and add newsletter signup This commit merges changes from l10-preview and main to update the poending pull request. - Filter prerendered paths to those with locale prefix - Fix issue #333: Remove LLM commentary in translated markdowns - Add timing and authentication diagnostics to Git operations - Fix markdown preprocessing (missing line ending - wituaread) --- netlify.toml | 2 - scripts/git-touch.sh | 68 ++++++++ scripts/git-untouch.sh | 18 +++ scripts/translation/git-ops.ts | 33 +++- scripts/translation/prompts.ts | 13 +- scripts/translation/translate-core.ts | 27 ++-- scripts/translation/translate.ts | 16 +- scripts/translation/utils.ts | 16 +- src/lib/adapter-patch-prerendered.js | 10 +- src/lib/components/Card.css | 5 + src/lib/components/Card.svelte | 10 +- src/lib/components/NewsletterSignup.svelte | 179 +++++++++++++++++++++ src/lib/components/Toc.svelte | 19 ++- src/posts/join.md | 13 +- src/posts/learn.md | 6 + src/posts/values.md | 1 + src/styles/styles.css | 2 +- 17 files changed, 390 insertions(+), 48 deletions(-) create mode 100755 scripts/git-touch.sh create mode 100755 scripts/git-untouch.sh create mode 100644 src/lib/components/Card.css create mode 100644 src/lib/components/NewsletterSignup.svelte diff --git a/netlify.toml b/netlify.toml index 4d372823d..94e42eae4 100644 --- a/netlify.toml +++ b/netlify.toml @@ -17,8 +17,6 @@ # Default build command. command = "pnpm run build" -[context.deploy-preview] - command = "pnpm run build:dev" # Use [dev] to set configuration overrides for local # development environments run using Netlify Dev - except # for environment variables. Environment variables for Netlify diff --git a/scripts/git-touch.sh b/scripts/git-touch.sh new file mode 100755 index 000000000..945e718bf --- /dev/null +++ b/scripts/git-touch.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Check if a file was provided +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +FILE="$1" + +# Check if the file exists +if [ ! -f "$FILE" ]; then + echo "Error: File '$FILE' not found." + exit 1 +fi + +# Check if the file has YAML frontmatter (starts with ---) +if ! grep -q "^---" "$FILE"; then + echo "Error: File does not have YAML frontmatter." + exit 1 +fi + +# Get current timestamp +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +# Find the first two occurrences of --- in the file to identify the frontmatter section +FIRST_LINE=$(grep -n "^---" "$FILE" | head -1 | cut -d':' -f1) +SECOND_LINE=$(grep -n "^---" "$FILE" | head -2 | tail -1 | cut -d':' -f1) + +if [ -z "$FIRST_LINE" ] || [ -z "$SECOND_LINE" ] || [ "$FIRST_LINE" -eq "$SECOND_LINE" ]; then + echo "Error: Could not properly identify YAML frontmatter boundaries." + exit 1 +fi + +# Create a temporary file for processing +TMP_FILE=$(mktemp) + +# Extract the frontmatter, modify it, then reconstruct the file +head -n "$FIRST_LINE" "$FILE" > "$TMP_FILE" +sed -n "$((FIRST_LINE+1)),$((SECOND_LINE-1))p" "$FILE" > "$TMP_FILE.frontmatter" + +# Check if git-touch already exists in frontmatter +if grep -q "git-touch:" "$TMP_FILE.frontmatter"; then + # Update the git-touch line + sed -i "s/^git-touch:.*$/git-touch: $TIMESTAMP/" "$TMP_FILE.frontmatter" +else + # Add git-touch as a new line + echo "git-touch: $TIMESTAMP" >> "$TMP_FILE.frontmatter" +fi + +# Append modified frontmatter to the temp file +cat "$TMP_FILE.frontmatter" >> "$TMP_FILE" +echo "---" >> "$TMP_FILE" + +# Append the rest of the original file after the frontmatter +tail -n +$((SECOND_LINE+1)) "$FILE" >> "$TMP_FILE" + +# Replace original with modified file +mv "$TMP_FILE" "$FILE" +rm -f "$TMP_FILE.frontmatter" + +# Stage the file +git add "$FILE" + +# Commit with message +git commit -m "git-touch $(basename "$FILE")" + +echo "Successfully touched and committed $(basename "$FILE")" \ No newline at end of file diff --git a/scripts/git-untouch.sh b/scripts/git-untouch.sh new file mode 100755 index 000000000..314bd6bd8 --- /dev/null +++ b/scripts/git-untouch.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Script to remove the git-touch field from markdown frontmatter in posts and translations +# Usage: ./git-untouch.sh + +set -e + +echo "Removing git-touch from markdown files..." + +# Process main posts directory +echo "Processing src/posts/*.md" +find src/posts -name "*.md" | xargs sed -i '/^git-touch:/d' + +# Process translation directories +echo "Processing src/temp/translations/md/*/*.md" +find src/temp/translations/md -path "src/temp/translations/md/*/*.md" | xargs sed -i '/^git-touch:/d' + +echo "Complete! Git-touch fields have been removed." \ No newline at end of file diff --git a/scripts/translation/git-ops.ts b/scripts/translation/git-ops.ts index e5ce4cf76..f04ffb941 100644 --- a/scripts/translation/git-ops.ts +++ b/scripts/translation/git-ops.ts @@ -111,9 +111,24 @@ export async function initializeGitCache(options: { console.log( `\ud83d\udd04 Setting up translation repository from ${options.repo} into ${options.dir}` ) + console.log(`Using ${options.token ? 'authenticated' : 'unauthenticated'} Git access`) + + // Time the clone operation + console.time('ā±ļø Git Clone') await options.git.clone(remote, options.dir) + console.timeEnd('ā±ļø Git Clone') + await options.git.cwd(options.dir) + // Test if we're authenticated by checking remote URL format + try { + const remoteUrl = await options.git.remote(['get-url', 'origin']) + const isAuthenticated = remoteUrl.includes('@') + console.log(`Authentication status: ${isAuthenticated ? 'SUCCESS' : 'FAILURE'}`) + } catch (err) { + console.log(`Failed to verify authentication: ${err.message}`) + } + // Always set git config in case we need to make local commits await options.git.addConfig('user.name', options.username) await options.git.addConfig('user.email', options.email) @@ -125,11 +140,24 @@ export async function initializeGitCache(options: { * @param git - The SimpleGit instance used to retrieve the log. * @returns A Promise that resolves to a Map where keys are file paths and values are the latest commit dates. */ -export async function getLatestCommitDates(git: SimpleGit): Promise> { +export async function getLatestCommitDates( + git: SimpleGit, + repoType: string = 'repo' +): Promise> { + console.log(`Starting git log retrieval for ${repoType} commit dates...`) const latestCommitDatesMap = new Map() + + const timerLabelLog = `ā±ļø Git Log Retrieval - ${repoType}` + console.time(timerLabelLog) const log = await git.log({ '--stat': 4096 }) + console.timeEnd(timerLabelLog) + + console.log(`Retrieved ${log.all.length} commits for ${repoType} date analysis`) + + const timerLabelParse = `ā±ļø Parse Git Log - ${repoType}` + console.time(timerLabelParse) for (const entry of log.all) { const files = entry.diff?.files if (!files) continue @@ -139,6 +167,9 @@ export async function getLatestCommitDates(git: SimpleGit): Promise { return `${commonHead(languageName, 'Markdown', promptAdditions)} -Maintain the structure of the document and don't modify script elements, HTML tags, link targets (including section links starting with a number sign) and file names. The content of social media widgets has to remain unchanged as well. I repeat: DO NOT CHANGE LINK TARGETS. DO NOT REMOVE SCRIPT TAGS OR COMPONENTS. The document starts with Frontmatter metadata enclosed in three dashes. Don't translate the keys of the metadata, only the values. Always make sure the result is valid Markdown syntax: +Maintain the structure of the document. Don't modify script elements, HTML tags, link targets (including section links starting with a number sign) and file names. The content of social media widgets has to remain unchanged as well. I repeat: DO NOT CHANGE LINK TARGETS. DO NOT REMOVE SCRIPT TAGS OR COMPONENTS. The document starts with Frontmatter metadata enclosed in three dashes. Don't translate the keys of the metadata, only the values. Always make sure the result is valid Markdown syntax: ${content} Do not start with \`\`\`md or new lines, just return the Markdown. + Translated Markdown:` } export function generateReviewPrompt(languageName: string) { - return `Please review and improve your translation to ${languageName} to ensure it meets the highest standards of quality. + return `Please review and (only if necessary) improve your translation to ${languageName} to ensure it meets the highest standards of quality. Focus on: 1. Accuracy of meaning compared to typical English expressions @@ -45,12 +46,16 @@ Focus on: 5. Maintaining the original formatting and technical elements Make necessary improvements while preserving: -- All technical elements (HTML tags, links, script elements) +- All technical elements (keys, HTML tags, links, script elements) - Document structure and formatting - Special instructions in comments - File names and technical terms when appropriate -Return the improved version in the same format, without any additional markup or explanations.` +Return the possibly improved text in the same format. + +All of your response will be used to generate website pages. Readers don't want to see your own translation notes. Do NOT tell us that you have done the work, or why particular decisions were made. That would make your response invalid. Only return the translation itself. + +Translation:` } function commonHead(languageName: string, format: string, promptAdditions: string) { diff --git a/scripts/translation/translate-core.ts b/scripts/translation/translate-core.ts index 82ebf6618..5957c6a16 100644 --- a/scripts/translation/translate-core.ts +++ b/scripts/translation/translate-core.ts @@ -50,7 +50,7 @@ export interface TranslationOptions { * or collects statistics in dry run mode without making API calls. * * @param content - The original content to be translated. - * @param promptGenerator - A function for generating the translation prompt. + * @param promptGenerators - Functions for generating the translation and review prompt. * @param language - The target language code. * @param promptAdditions - Additional context to include in the prompt. * @param options - Translation configuration options. @@ -60,7 +60,7 @@ export interface TranslationOptions { */ export async function translate( content: string, - promptGenerator: PromptGenerator, + promptGenerators: PromptGenerator[], language: string, promptAdditions: string, options: TranslationOptions, @@ -69,7 +69,8 @@ export async function translate( const languageName = options.languageNameGenerator.of(language) if (!languageName) throw new Error(`Couldn't resolve language code: ${language}`) - const translationPrompt = promptGenerator(languageName, content, promptAdditions) + const translationPrompt = promptGenerators[0](languageName, content, promptAdditions) + // Translation prompt ready // In dry run mode, collect statistics instead of making API calls if (options.isDryRun) { @@ -95,6 +96,7 @@ export async function translate( if (!firstPass) throw new Error(`Translation to ${languageName} failed`) if (options.verbose) { + console.log('First prompt: ', translationPrompt) console.log('First pass response:', firstPass) } else { console.log( @@ -102,9 +104,8 @@ export async function translate( ) } - // Second pass: review and refine translation with context - const reviewPrompt = `Please review your translation to ${languageName} for accuracy and naturalness. Make improvements where necessary but keep the meaning identical to the source.` - + // Secon d pass: review and refine translation with context + const reviewPrompt = promptGenerators[1](languageName) const reviewed = await postChatCompletion(options.llmClient, options.requestQueue, [ { role: 'user', content: translationPrompt }, { role: 'assistant', content: firstPass }, @@ -114,6 +115,7 @@ export async function translate( if (!reviewed) throw new Error(`Review of ${languageName} translation failed`) if (options.verbose) { + console.log('Review prompt: ', reviewPrompt) console.log('Review pass response:', reviewed) } else { console.log( @@ -136,7 +138,7 @@ export async function translateOrLoadMessages( options: { sourcePath: string languageTags: string[] - promptGenerator: PromptGenerator + promptGenerators: PromptGenerator[] targetDir: string cacheGitCwd: string logMessageFn?: (msg: string) => void @@ -147,7 +149,7 @@ export async function translateOrLoadMessages( { sourcePaths: [options.sourcePath], languageTags: options.languageTags, - promptGenerator: options.promptGenerator, + promptGenerators: options.promptGenerators, targetStrategy: (language) => path.join(options.targetDir, language + '.json'), cacheGitCwd: options.cacheGitCwd, logMessageFn: options.logMessageFn @@ -170,7 +172,7 @@ export async function translateOrLoadMarkdown( sourcePaths: string[] sourceBaseDir: string languageTags: string[] - promptGenerator: PromptGenerator + promptGenerators: PromptGenerator[] targetDir: string cacheGitCwd: string logMessageFn?: (msg: string) => void @@ -181,7 +183,7 @@ export async function translateOrLoadMarkdown( { sourcePaths: options.sourcePaths, languageTags: options.languageTags, - promptGenerator: options.promptGenerator, + promptGenerators: options.promptGenerators, targetStrategy: (language, sourcePath) => { const relativePath = path.relative(options.sourceBaseDir, sourcePath) return path.join(options.targetDir, language, relativePath) @@ -206,7 +208,7 @@ export async function translateOrLoad( options: { sourcePaths: string[] languageTags: string[] - promptGenerator: PromptGenerator + promptGenerators: PromptGenerator[] targetStrategy: TargetStrategy cacheGitCwd: string logMessageFn?: (msg: string) => void @@ -214,6 +216,7 @@ export async function translateOrLoad( translationOptions: TranslationOptions ): Promise<{ cacheCount: number; totalProcessed: number }> { const log = options.logMessageFn || console.log + // Log function ready let done = 1 let total = 0 let cacheCount = 0 @@ -267,7 +270,7 @@ export async function translateOrLoad( const translation = await translate( processedContent, - options.promptGenerator, + options.promptGenerators, languageTag, promptAdditions, translationOptions, diff --git a/scripts/translation/translate.ts b/scripts/translation/translate.ts index ef0a47890..252fd225c 100644 --- a/scripts/translation/translate.ts +++ b/scripts/translation/translate.ts @@ -20,7 +20,7 @@ import { initializeGitCache } from './git-ops' import { createLlmClient, createRequestQueue, LLM_DEFAULTS } from './llm-client' -import { generateJsonPrompt, generateMarkdownPrompt } from './prompts' +import { generateJsonPrompt, generateMarkdownPrompt, generateReviewPrompt } from './prompts' import { translateOrLoadMarkdown, translateOrLoadMessages } from './translate-core' import { requireEnvVar } from './utils' @@ -158,20 +158,21 @@ const languageNamesInEnglish = new Intl.DisplayNames('en', { type: 'language' }) email: GIT_CONFIG.EMAIL, git: cacheGit }) - translationOptions.cacheLatestCommitDates = await getLatestCommitDates(cacheGit) + translationOptions.cacheLatestCommitDates = await getLatestCommitDates(cacheGit, 'cache') })(), (async () => - (translationOptions.mainLatestCommitDates = await getLatestCommitDates(mainGit)))() + (translationOptions.mainLatestCommitDates = await getLatestCommitDates(mainGit, 'main')))() ]) // Process both message files and markdown files in parallel + // Begin message translation const results = await Promise.all([ (async () => { const result = await translateOrLoadMessages( { sourcePath: MESSAGE_SOURCE, languageTags: languageTags, - promptGenerator: generateJsonPrompt, + promptGenerators: [generateJsonPrompt, generateReviewPrompt], targetDir: MESSAGE_L10NS, cacheGitCwd: L10NS_BASE_DIR, logMessageFn: logMessage @@ -196,7 +197,7 @@ const languageNamesInEnglish = new Intl.DisplayNames('en', { type: 'language' }) sourcePaths: markdownPathsFromRoot, sourceBaseDir: MARKDOWN_SOURCE, languageTags: languageTags, - promptGenerator: generateMarkdownPrompt, + promptGenerators: [generateMarkdownPrompt, generateReviewPrompt], targetDir: MARKDOWN_L10NS, cacheGitCwd: L10NS_BASE_DIR, logMessageFn: logMessage @@ -221,6 +222,11 @@ const languageNamesInEnglish = new Intl.DisplayNames('en', { type: 'language' }) console.log(` - ${newTranslations} files needed new translations`) // Only push to Git if we actually created new translations + + // **We need to remove use of a single mainline for the repos very soon!** + // Currently developers can touch the repos easily, as can previews for pull requests etc. + // Any subsequent production deploy would show use the altereed in-development translations to viewers + if (newTranslations > 0) { console.log(`\nPushing translation changes to repository...`) await cacheGit.push() diff --git a/scripts/translation/utils.ts b/scripts/translation/utils.ts index c20917eda..6bce94693 100644 --- a/scripts/translation/utils.ts +++ b/scripts/translation/utils.ts @@ -19,11 +19,16 @@ export const MARKDOWN_CONFIG = { } // Comment patterns to preserve in markdown -export type PatternCommentPair = { pattern: RegExp; comment: string } +export type PatternCommentPair = { + pattern: RegExp + comment: string + lineBreakAfterComment?: boolean +} export const PREPROCESSING_COMMENT_AFTER_PATTERN: PatternCommentPair[] = [ { pattern: /---[\S\s]*?\n---\n/, - comment: `end of frontmatter metadata, dashes above need to stay` + comment: `end of frontmatter metadata, dashes above need to stay`, + lineBreakAfterComment: true }, { pattern: /\]\(#[a-z0-9-_.]+\)/g, @@ -163,8 +168,11 @@ export function preprocessMarkdown(source: string): string { return _0 }) } - for (const { pattern, comment } of PREPROCESSING_COMMENT_AFTER_PATTERN) { - processed = processed.replace(pattern, `$& `) + for (const { pattern, comment, lineBreakAfterComment } of PREPROCESSING_COMMENT_AFTER_PATTERN) { + processed = processed.replace( + pattern, + `$& ${lineBreakAfterComment ? '\n' : ''}` + ) } return processed } diff --git a/src/lib/adapter-patch-prerendered.js b/src/lib/adapter-patch-prerendered.js index 329d0f7de..ebd701c7e 100644 --- a/src/lib/adapter-patch-prerendered.js +++ b/src/lib/adapter-patch-prerendered.js @@ -12,11 +12,11 @@ export default function (adapter) { * @type {import('../../project.inlang/settings.json')} */ const settings = JSON.parse(fs.readFileSync('./project.inlang/settings.json', 'utf-8')) - //builder.prerendered.paths = builder.prerendered.paths.filter((path) => { - //for (const locale of settings.locales) { - //if (path.startsWith('/' + locale)) return true - //} - //}) + builder.prerendered.paths = builder.prerendered.paths.filter((path) => { + for (const locale of settings.locales) { + if (path.startsWith('/' + locale)) return true + } + }) adapter.adapt(builder) } } diff --git a/src/lib/components/Card.css b/src/lib/components/Card.css new file mode 100644 index 000000000..7918ba2f4 --- /dev/null +++ b/src/lib/components/Card.css @@ -0,0 +1,5 @@ +.card { + border-radius: 5px; + box-shadow: 0px 5px 20px 0px rgb(0, 0, 0, 0.1); + background-color: var(--bg-secondary); +} diff --git a/src/lib/components/Card.svelte b/src/lib/components/Card.svelte index d3d59117b..2d81462b8 100644 --- a/src/lib/components/Card.svelte +++ b/src/lib/components/Card.svelte @@ -1,15 +1,9 @@
- - diff --git a/src/lib/components/NewsletterSignup.svelte b/src/lib/components/NewsletterSignup.svelte new file mode 100644 index 000000000..43208b090 --- /dev/null +++ b/src/lib/components/NewsletterSignup.svelte @@ -0,0 +1,179 @@ + + + + + diff --git a/src/lib/components/Toc.svelte b/src/lib/components/Toc.svelte index c2effb008..70020a0d7 100644 --- a/src/lib/components/Toc.svelte +++ b/src/lib/components/Toc.svelte @@ -3,6 +3,7 @@ import Toc from 'svelte-toc' import X from 'lucide-svelte/icons/x' import List from 'lucide-svelte/icons/list' + import '$lib/components/Card.css' let desktop: boolean | undefined let open: boolean | undefined @@ -12,7 +13,7 @@ {#if open}
{/if} -
+
nav) { + background-color: inherit; + } + @media (hover: none) { :root { --toc-li-hover-color: var(--toc-li-color); diff --git a/src/posts/join.md b/src/posts/join.md index cdfc5cf33..fe2d5037c 100644 --- a/src/posts/join.md +++ b/src/posts/join.md @@ -2,6 +2,7 @@ title: Join PauseAI description: Sign up to join the PauseAI movement. --- + This is our nuclear moment. Rapid AI advancement represents one of history's most consequential and dangerous technological shifts. We demand politicians and companies pause AGI development until international safety agreements are established. @@ -13,6 +14,14 @@ Whether you can spare 5 minutes (sharing posts), an hour (flyering, writing lett After signing up, join our onboarding session online or locally to learn about current actions. The future of AI belongs to all of us. -_Is the form below not showing? Open [this link](https://airtable.com/embed/appWPTGqZmUcs3NWu/pag7ztLh27Omj5s2n/form) instead._ - + +_Is the form above not showing? Open [this link](https://airtable.com/embed/appWPTGqZmUcs3NWu/pag7ztLh27Omj5s2n/form) instead._ + + + +## Stay Updated + + diff --git a/src/posts/learn.md b/src/posts/learn.md index 38e70c6fe..1b36ca767 100644 --- a/src/posts/learn.md +++ b/src/posts/learn.md @@ -3,6 +3,12 @@ title: Learn why AI safety matters description: Educational resources (videos, articles, books) about AI risks and AI alignment --- + + + + ## On this website - [Risks](/risks). A summary of the risks of AI. diff --git a/src/posts/values.md b/src/posts/values.md index 470c711a5..dc80787da 100644 --- a/src/posts/values.md +++ b/src/posts/values.md @@ -2,6 +2,7 @@ title: PauseAI values description: How does PauseAI plan to achieve its mission? --- + ## What do we want? Globally halt frontier AI development until we know how to do it safely and under democratic control. See our [proposal](/proposal). diff --git a/src/styles/styles.css b/src/styles/styles.css index f3142fc77..15018e201 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -191,7 +191,7 @@ p { form { display: flex; flex-direction: column; - max-width: 20rem; + max-width: 30rem; } form label { From 3ac35c24a2c314395c16f9677d404d81ea370ff3 Mon Sep 17 00:00:00 2001 From: Anthony Bailey Date: Tue, 20 May 2025 06:35:45 +0100 Subject: [PATCH 3/6] fix: Update NewsletterSignup component to align with main implementation --- src/lib/components/NewsletterSignup.svelte | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/components/NewsletterSignup.svelte b/src/lib/components/NewsletterSignup.svelte index 43208b090..7de66fa4b 100644 --- a/src/lib/components/NewsletterSignup.svelte +++ b/src/lib/components/NewsletterSignup.svelte @@ -1,7 +1,4 @@ -{#if open} -
-{/if} +
Date: Tue, 20 May 2025 07:13:44 +0100 Subject: [PATCH 5/6] fix: Add missing Backdrop.svelte component for ToC blur effect The Backdrop component was missing but required by Toc.svelte. This fixes build errors while preserving the visual functionality. Note: There is a width issue with ToC in DE locale (German) that should be investigated separately by the ToC feature developer. --- src/lib/components/Backdrop.svelte | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/lib/components/Backdrop.svelte diff --git a/src/lib/components/Backdrop.svelte b/src/lib/components/Backdrop.svelte new file mode 100644 index 000000000..4bcf6167d --- /dev/null +++ b/src/lib/components/Backdrop.svelte @@ -0,0 +1,21 @@ + + +{#if open} +
+{/if} + + From 74bd357a7b756bcb70668fe34cbcc33ca16bf612 Mon Sep 17 00:00:00 2001 From: Anthony Bailey Date: Wed, 21 May 2025 16:35:03 +0100 Subject: [PATCH 6/6] Test: revert adapter logic change. --- src/lib/adapter-patch-prerendered.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/adapter-patch-prerendered.js b/src/lib/adapter-patch-prerendered.js index ebd701c7e..d278d2b30 100644 --- a/src/lib/adapter-patch-prerendered.js +++ b/src/lib/adapter-patch-prerendered.js @@ -11,12 +11,12 @@ export default function (adapter) { /** * @type {import('../../project.inlang/settings.json')} */ - const settings = JSON.parse(fs.readFileSync('./project.inlang/settings.json', 'utf-8')) - builder.prerendered.paths = builder.prerendered.paths.filter((path) => { - for (const locale of settings.locales) { - if (path.startsWith('/' + locale)) return true - } - }) + //const settings = JSON.parse(fs.readFileSync('./project.inlang/settings.json', 'utf-8')) + //builder.prerendered.paths = builder.prerendered.paths.filter((path) => { + // for (const locale of settings.locales) { + // if (path.startsWith('/' + locale)) return true + // } + //}) adapter.adapt(builder) } }