Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

TabularEditorDocs

This is the GitHub repository for the Tabular Editor documentation site, https://docs.tabulareditor.com. The repository contains documentation articles for both the open-source Tabular Editor 2.x as well as the commercial Tabular Editor 3, including articles for common features and C# scripting documentation.

Technical details

The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.

How to contribute

All contributions are welcome. We will review all pull requests submitted.

For convenience for typical contributions, we have built a simple wrapper around the build process. Unless you are working specifically on localization or the build process itself, you should be able to get by with the run script.

Getting started:

  1. Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
  2. From the repo root, run the setup check and install any tools it reports as missing
    • (Linux distros and macOS): ./run setup
    • (Windows): bash run setup (after this run everything from Git Bash)
  3. To iterate on docs and see a preview in your browser:
    • in one terminal: ./run serve: launches a localhost server that renders the docs at http://localhost:8080 where you can see the docs as they will be rendered on the docs website
    • in another terminal: ./run watch: regenerates the rendered site every time you save a change to a markdown document, so you can refresh your browser to see it

These commands all build and work only on the English language docs, as the English markdown is our canonical version of documentation and the language we expect contributions in.

For more info, try ./run help and ./run <subcommand> help. If you'd like more detail, check out the run script README.

If you want to have more control over the build process, continue reading below about the build-docs.py script.

Advanced build Script Usage

The build-docs.py script handles all documentation building tasks including multi-language support. Make sure you have Python >=3.11 and docfx installed, the latter either globally or locally.

Using build-docs.py directly

# Build and serve locally (English only, for development)
python build-docs.py --serve
# Or build all languages and serve with Azure Static Web Apps CLI
python build-docs.py --all
swa start _site

Commands

CommandDescription
python build-docs.pyBuild all languages (default)
python build-docs.py --allBuild all languages
python build-docs.py --lang enBuild English only
python build-docs.py --lang es zhBuild specific languages
python build-docs.py --listList available languages
python build-docs.py --serveBuild English and serve locally

Options

OptionDescription
--allBuild all available languages
--lang LANGSBuild specific language(s), space-separated
--listList available languages and exit
--serveBuild and serve locally (English only, for development)
--skip-genSkip running gen_redirects.py (use existing configs)
--no-api-copySkip copying API docs to localized sites
--skip-apiReuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases)
--permissiveDon't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict)
--syncSync English fallback for missing/outdated translations (for local dev)

What the Build Script Does

  1. Generates DocFX configurations - Runs gen_redirects.py to create docfx.json for each language
  2. Generates language manifest - Creates metadata/languages.json for runtime language switching
  3. Syncs content - Copies English source to localizedContent/en/. For other languages, only shared directories (assets, api) are synced by default since Crowdin manages translations. Use --sync to enable full English fallback for missing/outdated translations (useful for local development).
  4. Normalizes DocFX alerts - Runs normalize-localized-alerts.py on each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations)
  5. Stabilizes heading anchors - Runs normalize-localized-heading-anchors.py on each non-English language to inject English-slug bookmark anchors before translated headings, so #anchor cross-references resolve even when the heading text is translated (see Bookmark Links and Translations)
  6. Builds documentation - Runs DocFX for each requested language
  7. Fixes API docs - Patches xref links in generated API documentation
  8. Copies API docs - Shares English API docs with localized sites
  9. Injects SEO tags - Adds hreflang and canonical tags to HTML files
  10. Generates SWA config - Creates staticwebapp.config.json for Azure Static Web Apps routing

Project Structure

/
├── build-docs.py # Main build script
├── run # Task runner for common dev tasks (see build_scripts/run_scripts/README.md)
├── build_scripts/ # Helper scripts
│ ├── check_links.py # Dead-link checker for the generated _site
│ ├── config_loader.py # Shared configuration loader for build scripts
│ ├── csharp_doctest.py # Validates annotated C# code blocks in docs against the te CLI
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_sitemap_index.py # Post-processes the English sitemap; generates the sitemap index
│ ├── gen_staticwebapp_config.py # Generates Azure Static Web Apps routing config
│ ├── inject_seo_tags.py # Adds hreflang and canonical tags to built HTML
│ ├── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
│ ├── normalize-localized-heading-anchors.py # Injects English-slug bookmark anchors into translations
│ ├── sync-localized-content.py # Syncs English content into localized build dirs
│ ├── te_script_runner.py # Runs C# snippets against a throwaway model via the te CLI
│ ├── test-fixtures/ # Fixtures for the build-script tests
│ └── run_scripts/ # ./run subcommand scripts and shared lib.sh (see its README)
├── content/ # English source content (tracked in git)
│ └── _ui-strings.json # English UI strings (header, footer, banners)
├── localizedContent/ # Build directories for all languages
│ ├── en/ # English build (generated, gitignored)
│ └── {lang}/ # Translated content
│ ├── content/ # Translated markdown and UI strings (tracked)
│ │ └── _ui-strings.json # Translated UI strings for this language
│ └── docfx.json # Generated config (gitignored)
├── metadata/
│ ├── languages.json # Language manifest (generated)
│ ├── language-metadata.json # Language display names and RTL flags
│ └── redirects.json # URL redirects (server 301s and client meta-refresh)
├── docfx-template.json # Base DocFX configuration template
├── templates/ # DocFX templates
└── _site/ # Generated output
├── en/
├── es/
└── ...

Adding a New Language

  1. Create localizedContent/{lang}/content/ folder (e.g., fr/content/)
  2. Add the language entry to metadata/language-metadata.json with name and nativeName
  3. Add translated .md files to the content subdirectory
  4. Add a translated _ui-strings.json to the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated.
  5. Run python build-docs.py --all to generate configs and build. Language will be added dynamically to language picker.

Note: English content from content/ is automatically copied to localizedContent/en/content/ during build. For other languages, Crowdin manages translations via PRs. Shared directories (assets, api) are always synced from English. To use English as fallback for missing/outdated translations during local development, add the --sync flag.

Bookmark Links and Translations

When linking to a specific heading within a page (e.g., #my-heading), DocFX auto-generates the anchor ID from the heading text. Because Crowdin translates that text, the generated anchor changes per language (#model-io becomes #es-del-modelo, etc.), so a hardcoded English #anchor link breaks in every translated page and DocFX logs an InvalidBookmark warning. English builds stay clean because the anchors match there.

Automatic anchor stabilization (the build handles this)

build_scripts/normalize-localized-heading-anchors.py neutralizes this whole class of warning automatically. For each localized page it reads the matching English source, computes each heading's English slug, and injects a hidden bookmark anchor carrying that slug immediately before the corresponding translated heading:

<aid="model-io"data-loc-xref></a>
## E/S del modelo

DocFX accepts the injected id as a valid bookmark, so #model-io resolves and the link lands on the right section while the heading keeps its translated text. Headings are aligned to the English source positionally (Crowdin preserves heading structure); if the heading counts differ, the file is skipped and reported rather than risk a misaligned anchor. The script is idempotent (it strips its own data-loc-xref anchors before recomputing) and never modifies English.

The build runs it automatically for each non-English language before DocFX (step 5 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-heading-anchors.py # all languages
python build_scripts/normalize-localized-heading-anchors.py --dry-run # preview without writing
python build_scripts/normalize-localized-heading-anchors.py --check # exit 1 if changes are needed (CI)
python build_scripts/normalize-localized-heading-anchors.py es # a single language

Authoring guidance

  • Prefer the bracketed link form[text](xref:uid#anchor) over the bare @uid#anchor autolink. The closing ) delimits the anchor, so trailing punctuation in any language can never leak into it.

  • For a rename-proof anchor, add an explicit <a name="..."></a> tag above the heading. Crowdin does not translate HTML name attributes, so the anchor stays stable across all languages and survives English heading renames — unlike an auto-generated slug. Only add these to headings actually linked to; there is no need to add them everywhere.

    <aname="my-heading"></a>
    ## My Heading

DocFX Alerts and Translations

DocFX renders styled alert boxes (Note, Tip, Important, Warning, Caution) from a two-line blockquote where the marker stands alone on the first line:

> [!NOTE]> Your note text here.

When an alert like this is nested inside a list item, Crowdin collapses the two lines into one on export, producing > [!NOTE]> Your note text here.. DocFX requires the marker to be alone on its line, so the collapsed form is downgraded to a plain <blockquote> — losing the styled box — and the build logs an invalid-note-section warning. Only list-nested alerts are affected; top-level alerts round-trip through Crowdin unchanged.

build_scripts/normalize-localized-alerts.py repairs this by splitting the collapsed form back into two lines, preserving the original indentation so the alert stays inside its list item. It is idempotent and only rewrites the exact collapsed pattern (text inside fenced code blocks is left untouched), so it is safe to run repeatedly.

The build runs it automatically for each non-English language before DocFX (step 4 of What the Build Script Does). You can also run it manually after a Crowdin pull:

python build_scripts/normalize-localized-alerts.py # fix all languages
python build_scripts/normalize-localized-alerts.py --dry-run # preview without writing
python build_scripts/normalize-localized-alerts.py --check # exit 1 if fixes are needed (CI)
python build_scripts/normalize-localized-alerts.py es # fix a single language

Translating UI Strings

The _ui-strings.json file controls the text of site-wide UI elements that are not part of the documentation content itself: the header navigation, header buttons, footer text, and the AI translation warning banner. These strings are applied at runtime by the JavaScript bundle for non-English pages.

The English source is at content/_ui-strings.json. To provide translations for a language, create localizedContent/{lang}/content/_ui-strings.json with the same keys and translated values.

If a key is missing from a language's file, or no _ui-strings.json exists at all, the English value is used as fallback.

Available Keys

KeyEnglish valueElement
aiTranslationWarningThis content has been translated by AI...Warning banner shown on translated pages
header.nav.pricingPricingHeader nav link
header.nav.downloadDownloadHeader nav link
header.nav.learnLearnHeader nav link
header.nav.resourcesResourcesHeader nav dropdown toggle
header.nav.blogBlogResources dropdown item
header.nav.newsletterNewsletterResources dropdown item
header.nav.publicationsPublicationsResources dropdown item
header.nav.documentationDocumentationResources dropdown item
header.nav.supportCommunitySupport communityResources dropdown item
header.nav.contactUsContact UsHeader nav link
header.button1Free trialPrimary header CTA button
header.button2Main pageSecondary header button
footer.headingReady to get started?Footer section heading
footer.button1Try Tabular Editor 3Footer CTA button
footer.button2Buy Tabular Editor 3Footer CTA button
footer.aboutUsAbout usFooter left link
footer.contactUsContact usFooter left link
footer.technicalSupportTechnical SupportFooter left link
footer.privacyPolicyPrivacy & Cookie policyFooter bottom link
footer.termsConditionsTerms & ConditionsFooter bottom link
footer.licenseTermsLicense termsFooter bottom link
appliesToApplies to: "Applies to" label on article metadata
availableSinceAvailable sinceVersion availability label (e.g., "Available since 3.5.0")
availableInAvailable inVersion range label (e.g., "Available in 3.5.0–3.8.0")
inThisArticleIn this articleSidebar table of contents heading
searchResultsCount{count} results for "{query}"Search results summary
searchNoResultsNo results for "{query}"No search results message
tocFilterFilter by titleTOC filter input placeholder
nextArticleNextNext article navigation
prevArticlePreviousPrevious article navigation
themeLightLightTheme picker option
themeDarkDarkTheme picker option
themeAutoAutoTheme picker option
changeThemeChange themeTheme picker label
copyCopyCode block copy button
downloadPdfDownload PDFPDF download button
searchSearch documentationSearch input placeholder
noteNoteAlert box heading
warningWarningAlert box heading
tipTipAlert box heading
importantImportantAlert box heading
cautionCautionAlert box heading
tableOfContentsTable of ContentsMobile TOC offcanvas title
selectLanguageSelect languageLanguage picker label
copyCodeCopy codeCode block copy button aria-label

About

This is the articles for the Tabular Editor documentation site, https://docs.tabulareditor.com

Resources

Stars

18 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages