From 39f5d7f8c00f28f0ccef04f934a78160b0dea845 Mon Sep 17 00:00:00 2001 From: joshistoast Date: Mon, 20 Jul 2026 16:01:45 -0600 Subject: [PATCH 1/2] feat(docs): crowdin translations setup --- .github/workflows/deploy-docs.yml | 5 + crowdin.yml | 23 ++++ docs/astro.config.mjs | 21 +++ docs/package.json | 1 + docs/plugins/remark-localize-content.mjs | 127 ++++++++++++++++++ docs/plugins/remark-localize-content.test.mjs | 74 ++++++++++ docs/scripts/verify-deploy-output.mjs | 20 +++ docs/src/config/sidebar.ts | 40 ++++++ docs/src/content.config.ts | 41 +++++- .../content/docs/contributing/translations.md | 21 ++- .../docs/development/Documentation/index.mdx | 22 +-- docs/src/content/docs/index.mdx | 2 +- docs/src/content/i18n/en.json | 73 +++++----- docs/src/lib/base-path.ts | 9 ++ docs/src/lib/components/DownloadOptions.astro | 69 +++++----- docs/src/lib/components/EditLink.astro | 32 +++++ docs/src/lib/components/Footer.astro | 2 +- docs/src/lib/components/MarkdownContent.astro | 37 +++++ docs/src/lib/components/SettingsDocs.astro | 10 +- .../components/SystemRequirmentsLink.astro | 16 ++- 20 files changed, 534 insertions(+), 111 deletions(-) create mode 100644 crowdin.yml create mode 100644 docs/plugins/remark-localize-content.mjs create mode 100644 docs/plugins/remark-localize-content.test.mjs create mode 100644 docs/src/lib/components/EditLink.astro create mode 100644 docs/src/lib/components/MarkdownContent.astro diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 0e9c5774ba3..1a230368719 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -51,6 +51,7 @@ jobs: filters: | docs: - '.github/workflows/deploy-docs.yml' + - 'crowdin.yml' - 'docs/**' - 'scripts/generate_docs_json.py' - 'invokeai/app/**' @@ -111,6 +112,10 @@ jobs: run: pnpm run check-docs-data working-directory: docs + - name: test localization helpers + run: pnpm run test:localization + working-directory: docs + - name: build docs run: pnpm build working-directory: docs diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 00000000000..10d372206dc --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,23 @@ +preserve_hierarchy: true + +files: + - source: /docs/src/content/docs/index.mdx + translation: /docs/src/content/docs/%two_letters_code%/%original_file_name% + + - source: /docs/src/content/docs/start-here/**/*.{md,mdx} + translation: /docs/src/content/docs/%two_letters_code%/start-here/**/%original_file_name% + + - source: /docs/src/content/docs/configuration/**/*.{md,mdx} + translation: /docs/src/content/docs/%two_letters_code%/configuration/**/%original_file_name% + + - source: /docs/src/content/docs/concepts/**/*.{md,mdx} + translation: /docs/src/content/docs/%two_letters_code%/concepts/**/%original_file_name% + + - source: /docs/src/content/docs/features/**/*.{md,mdx} + translation: /docs/src/content/docs/%two_letters_code%/features/**/%original_file_name% + + - source: /docs/src/content/docs/troubleshooting/**/*.{md,mdx} + translation: /docs/src/content/docs/%two_letters_code%/troubleshooting/**/%original_file_name% + + - source: /docs/src/content/i18n/en.json + translation: /docs/src/content/i18n/%two_letters_code%.json diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index ebb59d36115..1984590b58a 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -7,6 +7,7 @@ import starlightLinksValidator from 'starlight-links-validator'; import starlightLlmsText from 'starlight-llms-txt'; import starlightChangelogs from 'starlight-changelogs'; import { rehypePrefixBaseToRootLinks } from './plugins/rehype-prefix-base-to-root-links.mjs'; +import { remarkLocalizeContent } from './plugins/remark-localize-content.mjs'; import starlightContextualMenu from 'starlight-contextual-menu'; // Configs @@ -34,6 +35,7 @@ export default defineConfig({ site, base: base || undefined, markdown: { + remarkPlugins: [[remarkLocalizeContent, { locales: ['de', 'es', 'hi'] }]], rehypePlugins: [[rehypePrefixBaseToRootLinks, { base }]], }, integrations: [ @@ -41,6 +43,9 @@ export default defineConfig({ // Content title: { en: 'InvokeAI Documentation', + de: 'InvokeAI-Dokumentation', + es: 'Documentación de InvokeAI', + hi: 'InvokeAI दस्तावेज़', }, logo: { src: './src/assets/invoke-icon-wide.svg', @@ -58,6 +63,18 @@ export default defineConfig({ label: 'English', lang: 'en', }, + de: { + label: 'Deutsch', + lang: 'de', + }, + es: { + label: 'Español', + lang: 'es', + }, + hi: { + label: 'हिन्दी', + lang: 'hi', + }, }, social: socialConfig, tableOfContents: { @@ -73,12 +90,16 @@ export default defineConfig({ ThemeProvider: './src/lib/components/ForceDarkTheme.astro', ThemeSelect: './src/lib/components/EmptyComponent.astro', Footer: './src/lib/components/Footer.astro', + EditLink: './src/lib/components/EditLink.astro', + MarkdownContent: './src/lib/components/MarkdownContent.astro', PageFrame: './src/layouts/PageFrameExtended.astro', }, plugins: [ starlightLinksValidator({ errorOnRelativeLinks: false, errorOnLocalLinks: false, + // The validator only knows content collection routes, not custom Astro pages. + exclude: ['/download/'], }), starlightLlmsText(), starlightChangelogs(), diff --git a/docs/package.json b/docs/package.json index 29d0786e8ea..23f700f22e5 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,6 +9,7 @@ "check-docs-data": "pnpm run generate-docs-data && git diff --exit-code -- src/generated", "check-deploy-output": "node ./scripts/verify-deploy-output.mjs", "check-redirects": "node ./scripts/validate-redirect-targets.mjs", + "test:localization": "node --test ./plugins/remark-localize-content.test.mjs", "build": "astro build", "preview": "astro preview", "astro": "astro" diff --git a/docs/plugins/remark-localize-content.mjs b/docs/plugins/remark-localize-content.mjs new file mode 100644 index 00000000000..86f22516963 --- /dev/null +++ b/docs/plugins/remark-localize-content.mjs @@ -0,0 +1,127 @@ +import { existsSync } from 'node:fs'; +import { dirname, relative, resolve, sep } from 'node:path'; + +const relativeImportPattern = /(from\s+|import\s*)(['"])(\.\.?\/[^'"]+)\2/g; + +export function remarkLocalizeContent(options = {}) { + const locales = new Set(options.locales ?? []); + + return (tree, file) => { + const context = getLocalizedFileContext(file?.path, locales); + + if (!context) { + return; + } + + walk(tree, (node) => { + if (node.type === 'link' && typeof node.url === 'string') { + node.url = localizeRootLink(node.url, context.locale); + } + + if (node.type === 'image' && typeof node.url === 'string') { + node.url = pointToSourceAsset(node.url, context); + } + + if (node.type === 'mdxjsEsm' && typeof node.value === 'string') { + node.value = node.value.replace(relativeImportPattern, (match, prefix, quote, specifier) => { + const localizedSpecifier = pointToSourceAsset(specifier, context); + return `${prefix}${quote}${localizedSpecifier}${quote}`; + }); + } + + if (node.type === 'mdxJsxAttribute' && typeof node.value === 'string') { + if (node.name === 'href' || node.name === 'link') { + node.value = localizeRootLink(node.value, context.locale); + } else if (node.name === 'src') { + node.value = pointToSourceAsset(node.value, context); + } + } + }); + }; +} + +function getLocalizedFileContext(filePath, locales) { + if (typeof filePath !== 'string') { + return undefined; + } + + const normalizedPath = filePath.split(sep).join('/'); + const docsMarker = '/src/content/docs/'; + const markerIndex = normalizedPath.lastIndexOf(docsMarker); + + if (markerIndex === -1) { + return undefined; + } + + const docsRoot = normalizedPath.slice(0, markerIndex + docsMarker.length - 1); + const pathWithinDocs = normalizedPath.slice(markerIndex + docsMarker.length); + const [locale, ...sourceSegments] = pathWithinDocs.split('/'); + + if (!locales.has(locale) || sourceSegments.length === 0) { + return undefined; + } + + return { + locale, + localizedDirectory: dirname(normalizedPath), + sourceDirectory: dirname(resolve(docsRoot, ...sourceSegments)), + }; +} + +function localizeRootLink(url, locale) { + if (!url.startsWith('/') || url.startsWith('//')) { + return url; + } + + if ( + url === `/${locale}` || + url.startsWith(`/${locale}/`) || + url === '/download' || + url.startsWith('/download/') + ) { + return url; + } + + return `/${locale}${url}`; +} + +function pointToSourceAsset(url, context) { + if (!url.startsWith('./') && !url.startsWith('../')) { + return url; + } + + const suffixIndex = url.search(/[?#]/); + const pathPart = suffixIndex === -1 ? url : url.slice(0, suffixIndex); + const suffix = suffixIndex === -1 ? '' : url.slice(suffixIndex); + const sourceTarget = resolve(context.sourceDirectory, pathPart); + + if (!existsSync(sourceTarget)) { + return url; + } + + const localizedPath = relative(context.localizedDirectory, sourceTarget).split(sep).join('/'); + const normalizedPath = localizedPath.startsWith('.') ? localizedPath : `./${localizedPath}`; + return `${normalizedPath}${suffix}`; +} + +function walk(node, visitor) { + if (!node || typeof node !== 'object') { + return; + } + + visitor(node); + + if (!Array.isArray(node.children)) { + return; + } + + for (const child of node.children) { + walk(child, visitor); + } +} + +export const testing = { + getLocalizedFileContext, + localizeRootLink, + pointToSourceAsset, +}; diff --git a/docs/plugins/remark-localize-content.test.mjs b/docs/plugins/remark-localize-content.test.mjs new file mode 100644 index 00000000000..010d2e4b502 --- /dev/null +++ b/docs/plugins/remark-localize-content.test.mjs @@ -0,0 +1,74 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { remarkLocalizeContent, testing } from './remark-localize-content.mjs'; + +const docsRoot = fileURLToPath(new URL('../src/content/docs/', import.meta.url)); + +test('localizes links and reuses source assets in translated MDX', () => { + const tree = { + type: 'root', + children: [ + { type: 'link', url: '/concepts/prompting-guide/', children: [] }, + { type: 'link', url: 'https://example.com/', children: [] }, + { type: 'image', url: './assets/gallery.png', children: [] }, + { + type: 'mdxJsxFlowElement', + children: [ + { type: 'mdxJsxAttribute', name: 'href', value: '/troubleshooting/faq/' }, + { type: 'mdxJsxAttribute', name: 'href', value: '/download/' }, + ], + }, + ], + }; + + remarkLocalizeContent({ locales: ['de', 'es', 'hi'] })(tree, { + path: `${docsRoot}es/features/gallery.mdx`, + }); + + assert.equal(tree.children[0].url, '/es/concepts/prompting-guide/'); + assert.equal(tree.children[1].url, 'https://example.com/'); + assert.equal(tree.children[2].url, '../../features/assets/gallery.png'); + assert.equal(tree.children[3].children[0].value, '/es/troubleshooting/faq/'); + assert.equal(tree.children[3].children[1].value, '/download/'); +}); + +test('rewrites relative MDX asset imports to the English source tree', () => { + const tree = { + type: 'root', + children: [ + { + type: 'mdxjsEsm', + value: "import splashImage from './assets/invoke-webui-canvas.png';", + children: [], + }, + ], + }; + + remarkLocalizeContent({ locales: ['de', 'es', 'hi'] })(tree, { + path: `${docsRoot}hi/index.mdx`, + }); + + assert.equal( + tree.children[0].value, + "import splashImage from '../assets/invoke-webui-canvas.png';", + ); +}); + +test('leaves English source files unchanged', () => { + const tree = { + type: 'root', + children: [{ type: 'link', url: '/configuration/docker/', children: [] }], + }; + + remarkLocalizeContent({ locales: ['de', 'es', 'hi'] })(tree, { + path: `${docsRoot}index.mdx`, + }); + + assert.equal(tree.children[0].url, '/configuration/docker/'); + assert.equal( + testing.getLocalizedFileContext(`${docsRoot}it/index.mdx`, new Set(['de', 'es', 'hi'])), + undefined, + ); +}); diff --git a/docs/scripts/verify-deploy-output.mjs b/docs/scripts/verify-deploy-output.mjs index 885d1f44baa..037646a07e7 100644 --- a/docs/scripts/verify-deploy-output.mjs +++ b/docs/scripts/verify-deploy-output.mjs @@ -3,6 +3,8 @@ import { readFileSync } from 'node:fs'; const deployTarget = process.env.DEPLOY_TARGET ?? 'custom'; const base = deployTarget === 'ghpages' ? '/InvokeAI' : ''; const withBase = (path) => `${base}${path}`; +const siteUrl = (path) => + deployTarget === 'ghpages' ? `https://invoke-ai.github.io${base}${path}` : `https://invoke.ai${path}`; const expectations = [ { @@ -45,6 +47,24 @@ const expectations = [ }, ]; +for (const locale of ['de', 'es', 'hi']) { + expectations.push({ + file: `${locale}/start-here/installation/index.html`, + includes: [ + ` - - src/ - - content/ - - docs/ - - start-here/ - - installation.mdx - - i18n/ - - zh-CN Country code here - - start-here/ - - installation.mdx - - -We recommend simply copy/pasting the file and rewriting the text from there. - -Learn more about the intricacies of translating Astro Starlight docs [here](https://starlight.astro.build/guides/i18n). +Do not manually edit exported locale files. Make English documentation changes in GitHub and translation changes in Crowdin. Crowdin maintains a translation pull request, and the documentation workflow builds every locale before the PR is merged. ## Running a Build diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index a0c7e3e67c6..7cfbf5357b4 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -84,7 +84,7 @@ Whether you are looking to install the app, get support, train your own models, Ready to dive in? The [Invoke Launcher](/start-here/installation/) is the fastest way to get up and running on Windows, macOS, and Linux. For advanced setups, try [Docker](/configuration/docker/) or a [manual Python installation](/start-here/manual/). - + Get Invoke diff --git a/docs/src/content/i18n/en.json b/docs/src/content/i18n/en.json index 69333e3a0b2..e9f13c64db2 100644 --- a/docs/src/content/i18n/en.json +++ b/docs/src/content/i18n/en.json @@ -1,45 +1,32 @@ { - "skipLink.label": "Skip to content", - "search.label": "Search", - "search.ctrlKey": "Ctrl", - "search.cancelLabel": "Cancel", - "search.devWarning": "Search is only available in production builds. \nTry building and previewing the site to test it out locally.", - "themeSelect.accessibleLabel": "Select theme", - "themeSelect.dark": "Dark", - "themeSelect.light": "Light", - "themeSelect.auto": "Auto", - "languageSelect.accessibleLabel": "Select language", - "menuButton.accessibleLabel": "Menu", - "sidebarNav.accessibleLabel": "Main", - "tableOfContents.onThisPage": "On this page", - "tableOfContents.overview": "Overview", - "i18n.untranslatedContent": "This content is not available in your language yet.", - "page.editLink": "Edit page", - "page.lastUpdated": "Last updated:", - "page.previousLink": "Previous", - "page.nextLink": "Next", - "page.draft": "This content is a draft and will not be included in production builds.", - "404.text": "Page not found. Check the URL or try using the search bar.", - "aside.note": "Note", - "aside.tip": "Tip", - "aside.caution": "Caution", - "aside.danger": "Danger", - "fileTree.directory": "Directory", - "builtWithStarlight.label": "Built with Starlight", - "heading.anchorLabel": "Section titled “{{title}}”", - - "expressiveCode.copyButtonCopied": "Copied!", - "expressiveCode.copyButtonTooltip": "Copy to clipboard", - "expressiveCode.terminalWindowFallbackTitle": "Terminal window", - - "pagefind.clear_search": "Clear", - "pagefind.load_more": "Load more results", - "pagefind.search_label": "Search this site", - "pagefind.filters_label": "Filters", - "pagefind.zero_results": "No results for [SEARCH_TERM]", - "pagefind.many_results": "[COUNT] results for [SEARCH_TERM]", - "pagefind.one_result": "[COUNT] result for [SEARCH_TERM]", - "pagefind.alt_search": "No results for [SEARCH_TERM]. Showing results for [DIFFERENT_TERM] instead", - "pagefind.search_suggestion": "No results for [SEARCH_TERM]. Try one of the following searches:", - "pagefind.searching": "Searching for [SEARCH_TERM]..." + "footer.builtBy": "This site was designed and developed by", + "page.translateLink": "Translate this page", + "download.windows.headline": "Download for Windows", + "download.windows.note": "Requires Windows 10 or later, and NVIDIA or AMD GPU.", + "download.windows.action": "Download EXE", + "download.macos.headline": "Download for macOS", + "download.macos.note": "Requires Apple Silicon (M-Series). Not compatible with Intel.", + "download.macos.action": "Download DMG", + "download.linux.headline": "Download for Linux", + "download.linux.note": "Requires NVIDIA or AMD GPU. Compatible with most distributions.", + "download.linux.action": "Download AppImage", + "download.github.headline": "Download from GitHub", + "download.github.description": "For advanced users who want to set up Invoke manually or contribute to the project.", + "download.docker.headline": "Run with Docker", + "download.docker.description": "For users who want to run Invoke without installing dependencies directly on their system.", + "download.hosted.heading": "Hosted Options", + "download.hosted.description": "For users who want to run Invoke on a hosted GPU service instead of their own hardware.", + "download.aibadgr.headline": "Run on AI Badgr", + "download.aibadgr.description": "Run on the AI Badgr hosted GPU service.", + "download.runpod.headline": "Run on RunPod", + "download.runpod.description": "Run on the RunPod hosted GPU service.", + "download.railway.headline": "Run on Railway", + "download.railway.description": "Run on the Railway hosted GPU service.", + "download.separator": "OR", + "systemRequirements.title": "System Requirements", + "systemRequirements.description": "Please check the system requirements page to make sure your hardware is capable of running the desired models.", + "settings.type": "Type", + "settings.default": "Default", + "settings.environment": "Env", + "settings.values": "Values:" } diff --git a/docs/src/lib/base-path.ts b/docs/src/lib/base-path.ts index 23042d899a5..8e80ab7f70c 100644 --- a/docs/src/lib/base-path.ts +++ b/docs/src/lib/base-path.ts @@ -4,3 +4,12 @@ export const withBase = (path: string, baseUrl: string) => { return `${normalizedBase}${normalizedPath}`; }; + +export const localizePath = (path: string, locale?: string) => { + if (!locale) { + return path; + } + + const normalizedPath = path.startsWith('/') ? path : `/${path}`; + return `/${locale}${normalizedPath}`; +}; diff --git a/docs/src/lib/components/DownloadOptions.astro b/docs/src/lib/components/DownloadOptions.astro index 95124322fd9..51a73e4d28b 100644 --- a/docs/src/lib/components/DownloadOptions.astro +++ b/docs/src/lib/components/DownloadOptions.astro @@ -1,7 +1,16 @@ --- import { CardGrid, LinkCard, Icon, LinkButton } from '@astrojs/starlight/components'; import { type StarlightIcon } from '@astrojs/starlight/types'; -import { withBase } from '../base-path'; +import { localizePath, withBase } from '../base-path'; + +const t = Astro.locals.t; +let locale: string | undefined; +try { + locale = Astro.locals.starlightRoute.locale; +} catch { + // Content is also rendered outside a Starlight route when generating llms-full.txt. + locale = undefined; +} type LauncherDownloadOption = { icon: StarlightIcon; @@ -13,59 +22,59 @@ type LauncherDownloadOption = { const launcherDownloadOptions: Record = { windows: { icon: 'seti:windows', - headline: 'Download for Windows', - note: 'Requires Windows 10 or later, and NVIDIA or AMD GPU.', + headline: t('download.windows.headline'), + note: t('download.windows.note'), launcherDownloadLink: 'https://github.com/invoke-ai/launcher/releases/latest/download/Invoke.Community.Edition.Setup.latest.exe', - launcherDownloadLabel: 'Download EXE', + launcherDownloadLabel: t('download.windows.action'), }, macos: { icon: 'apple', - headline: 'Download for MacOS', - note: 'Requires Apple Silicon (M-Series). Not compatible with Intel.', + headline: t('download.macos.headline'), + note: t('download.macos.note'), launcherDownloadLink: 'https://github.com/invoke-ai/launcher/releases/latest/download/Invoke.Community.Edition-latest-arm64.dmg', - launcherDownloadLabel: 'Download DMG', + launcherDownloadLabel: t('download.macos.action'), }, linux: { icon: 'linux', - headline: 'Download for Linux', - note: 'Requires NVIDIA or AMD GPU. Compatible with most distributions.', + headline: t('download.linux.headline'), + note: t('download.linux.note'), launcherDownloadLink: 'https://github.com/invoke-ai/launcher/releases/latest/download/Invoke.Community.Edition-latest.AppImage', - launcherDownloadLabel: 'Download AppImage', + launcherDownloadLabel: t('download.linux.action'), }, }; const manualDownloadOptions = { github: { - headline: 'Download from GitHub', - description: 'For advanced users who want to set up Invoke manually or contribute to the project.', + headline: t('download.github.headline'), + description: t('download.github.description'), href: 'https://github.com/invoke-ai/InvokeAI/releases', }, docker: { - headline: 'Run with Docker', - description: 'For users who want to run Invoke without installing dependencies directly on their system.', - href: withBase('/configuration/docker/', import.meta.env.BASE_URL), + headline: t('download.docker.headline'), + description: t('download.docker.description'), + href: withBase(localizePath('/configuration/docker/', locale), import.meta.env.BASE_URL), }, }; const hostedOptions = { aibadgr: { - headline: 'Run on AI Badgr', - description: 'Run on the AI Badgr hosted GPU service', - href: 'https://aibadgr.com/gpu/launch?template=invokeai', - }, + headline: t('download.aibadgr.headline'), + description: t('download.aibadgr.description'), + href: 'https://aibadgr.com/gpu/launch?template=invokeai', + }, runpod: { - headline: 'Run on RunPod', - description: 'Run on the RunPod hosted GPU service', - href: 'https://www.runpod.io/blog/invoke-ai-stable-diffusion-runpod-nfz18', - }, + headline: t('download.runpod.headline'), + description: t('download.runpod.description'), + href: 'https://www.runpod.io/blog/invoke-ai-stable-diffusion-runpod-nfz18', + }, railway: { - headline: 'Run on Railway', - description: 'Run on the Railway hosted GPU service', - href: 'https://railway.com/deploy/invokeai', - }, + headline: t('download.railway.headline'), + description: t('download.railway.description'), + href: 'https://railway.com/deploy/invokeai', + }, }; --- @@ -99,7 +108,7 @@ const hostedOptions = {
- OR + {t('download.separator')}
@@ -113,8 +122,8 @@ const hostedOptions = {
-

Hosted Options

-

For users who want to run Invoke on a hosted GPU service instead of their own hardware.

+

{t('download.hosted.heading')}

+

{t('download.hosted.description')}

{ diff --git a/docs/src/lib/components/EditLink.astro b/docs/src/lib/components/EditLink.astro new file mode 100644 index 00000000000..51e89b548f5 --- /dev/null +++ b/docs/src/lib/components/EditLink.astro @@ -0,0 +1,32 @@ +--- +import { Icon } from '@astrojs/starlight/components'; + +const crowdinProjectUrl = 'https://crowdin.com/project/invokeai-docs'; +const { editUrl, locale } = Astro.locals.starlightRoute; +const href = locale ? crowdinProjectUrl : editUrl; +const label = locale ? Astro.locals.t('page.translateLink') : Astro.locals.t('page.editLink'); +--- + +{ + href && ( + + + {label} + + ) +} + + diff --git a/docs/src/lib/components/Footer.astro b/docs/src/lib/components/Footer.astro index 65137dec3b9..00c21017db4 100644 --- a/docs/src/lib/components/Footer.astro +++ b/docs/src/lib/components/Footer.astro @@ -5,7 +5,7 @@ import PageFooter from '@astrojs/starlight/components/Footer.astro';
- This site was designed and developed by Aether Fox Studio
+ +{ + locale && ( + + ) +} diff --git a/docs/src/lib/components/SettingsDocs.astro b/docs/src/lib/components/SettingsDocs.astro index c539b27cd46..e1c9e2c9d6e 100644 --- a/docs/src/lib/components/SettingsDocs.astro +++ b/docs/src/lib/components/SettingsDocs.astro @@ -1,6 +1,8 @@ --- import settingsData from '../../generated/settings.json'; +const t = Astro.locals.t; + const groupedSettings = Object.entries( settingsData.settings.reduce((groups, setting) => { const category = setting.category || 'OTHER'; @@ -62,15 +64,15 @@ const formatCategoryName = (category) => {
- Type + {t('settings.type')} {formatType(setting.type)}
- Default + {t('settings.default')} {formatValue(setting.default)}
- Env + {t('settings.environment')} {setting.env_var}
@@ -78,7 +80,7 @@ const formatCategoryName = (category) => { {setting.description} {setting.literal_values.length > 0 && ( - Values: {setting.literal_values.map((v) => {formatValue(v)}).reduce((prev, curr) => [prev, ' ', curr])} + {t('settings.values')} {setting.literal_values.map((v) => {formatValue(v)}).reduce((prev, curr) => [prev, ' ', curr])} )} {Object.keys(setting.validation).length > 0 && ( diff --git a/docs/src/lib/components/SystemRequirmentsLink.astro b/docs/src/lib/components/SystemRequirmentsLink.astro index 16455b8e749..64821d35862 100644 --- a/docs/src/lib/components/SystemRequirmentsLink.astro +++ b/docs/src/lib/components/SystemRequirmentsLink.astro @@ -1,10 +1,18 @@ --- import { LinkCard } from '@astrojs/starlight/components'; -import { withBase } from '../base-path'; +import { localizePath, withBase } from '../base-path'; + +let locale: string | undefined; +try { + locale = Astro.locals.starlightRoute.locale; +} catch { + // Content is also rendered outside a Starlight route when generating llms-full.txt. + locale = undefined; +} --- From 2e8f9c57352342ec6851dfab2c92568ebeb40aec Mon Sep 17 00:00:00 2001 From: joshistoast Date: Mon, 20 Jul 2026 16:22:55 -0600 Subject: [PATCH 2/2] fix(docs): update crowdin project urls --- docs/scripts/verify-deploy-output.mjs | 2 +- docs/src/content/docs/contributing/translations.md | 4 ++-- docs/src/content/docs/development/Documentation/index.mdx | 2 +- docs/src/lib/components/EditLink.astro | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/scripts/verify-deploy-output.mjs b/docs/scripts/verify-deploy-output.mjs index 037646a07e7..d99b2a8de4d 100644 --- a/docs/scripts/verify-deploy-output.mjs +++ b/docs/scripts/verify-deploy-output.mjs @@ -55,7 +55,7 @@ for (const locale of ['de', 'es', 'hi']) { 'lang="en" dir="ltr"', `hreflang="${locale}" href="${siteUrl(`/${locale}/start-here/installation/`)}`, `href="${withBase(`/${locale}/start-here/system-requirements/`)}`, - 'href="https://crowdin.com/project/invokeai-docs"', + 'href="https://crowdin.com/project/invoke"', 'data-pagefind-body', ], excludes: [ diff --git a/docs/src/content/docs/contributing/translations.md b/docs/src/content/docs/contributing/translations.md index bac7d5551c9..6773cd627e9 100644 --- a/docs/src/content/docs/contributing/translations.md +++ b/docs/src/content/docs/contributing/translations.md @@ -4,14 +4,14 @@ title: Translations InvokeAI uses two translation services: -- [Crowdin](https://crowdin.com/project/invokeai-docs) for this documentation site. +- [Crowdin](https://crowdin.com/project/invoke) for this documentation site. - [Weblate](https://hosted.weblate.org/engage/invokeai/) for text in the InvokeAI application. The initial documentation languages are German, Spanish, and Hindi. Pages that are not translated yet display the English content with an untranslated-content notice. Partially translated pages may retain some English text. ## Translating the documentation -Visit the [InvokeAI Docs project on Crowdin](https://crowdin.com/project/invokeai-docs), sign in, and select German, Spanish, or Hindi. Crowdin preserves Markdown structure, code examples, links, and other non-translatable syntax while presenting the page text for translation. +Visit the [InvokeAI Docs project on Crowdin](https://crowdin.com/project/invoke), sign in, and select German, Spanish, or Hindi. Crowdin preserves Markdown structure, code examples, links, and other non-translatable syntax while presenting the page text for translation. English documentation is maintained in GitHub and synchronized to Crowdin. Completed translations are returned in an automated pull request, checked by the normal documentation build, and merged by an InvokeAI maintainer. Do not edit files inside the `docs/src/content/docs/de`, `es`, or `hi` directories directly because Crowdin owns those generated files. diff --git a/docs/src/content/docs/development/Documentation/index.mdx b/docs/src/content/docs/development/Documentation/index.mdx index 942e897e974..491c5b0deae 100644 --- a/docs/src/content/docs/development/Documentation/index.mdx +++ b/docs/src/content/docs/development/Documentation/index.mdx @@ -198,7 +198,7 @@ To render the image, you'd just use a relative path in your markdown. ### Adding Translations -Documentation translations are managed in the [InvokeAI Docs Crowdin project](https://crowdin.com/project/invokeai-docs). English Markdown and MDX files in `src/content/docs` are the source of truth. Crowdin exports German, Spanish, and Hindi pages into locale directories under that same collection, matching [Starlight's internationalized content layout](https://starlight.astro.build/guides/i18n/). +Documentation translations are managed in the [InvokeAI Docs Crowdin project](https://crowdin.com/project/invoke). English Markdown and MDX files in `src/content/docs` are the source of truth. Crowdin exports German, Spanish, and Hindi pages into locale directories under that same collection, matching [Starlight's internationalized content layout](https://starlight.astro.build/guides/i18n/). Do not manually edit exported locale files. Make English documentation changes in GitHub and translation changes in Crowdin. Crowdin maintains a translation pull request, and the documentation workflow builds every locale before the PR is merged. diff --git a/docs/src/lib/components/EditLink.astro b/docs/src/lib/components/EditLink.astro index 51e89b548f5..943f70b60e1 100644 --- a/docs/src/lib/components/EditLink.astro +++ b/docs/src/lib/components/EditLink.astro @@ -1,7 +1,7 @@ --- import { Icon } from '@astrojs/starlight/components'; -const crowdinProjectUrl = 'https://crowdin.com/project/invokeai-docs'; +const crowdinProjectUrl = 'https://crowdin.com/project/invoke'; const { editUrl, locale } = Astro.locals.starlightRoute; const href = locale ? crowdinProjectUrl : editUrl; const label = locale ? Astro.locals.t('page.translateLink') : Astro.locals.t('page.editLink');