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.
The site uses DocFX and GitHub flavoured markdown for all articles. Multi-language support is provided through the localizedContent/ directory.
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:
- Make sure you have Bash installed (included in most Linux distros, macOS, and Git for Windows)
- 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)
- (Linux distros and macOS):
- 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
- in one terminal:
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.
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.
# 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| Command | Description |
|---|---|
python build-docs.py |
Build all languages (default) |
python build-docs.py --all |
Build all languages |
python build-docs.py --lang en |
Build English only |
python build-docs.py --lang es zh |
Build specific languages |
python build-docs.py --list |
List available languages |
python build-docs.py --serve |
Build English and serve locally |
| Option | Description |
|---|---|
--all |
Build all available languages |
--lang LANGS |
Build specific language(s), space-separated |
--list |
List available languages and exit |
--serve |
Build and serve locally (English only, for development) |
--skip-gen |
Skip running gen_redirects.py (use existing configs) |
--no-api-copy |
Skip copying API docs to localized sites |
--skip-api |
Reuse existing API metadata in content/api, ~30-40% faster (local markdown iteration only, requires --serve/--lang; never for testing/CI/CD/releases) |
--permissive |
Don't treat English DocFX warnings as build failures (for local iteration; full/CI builds stay strict) |
--sync |
Sync English fallback for missing/outdated translations (for local dev) |
- Generates DocFX configurations - Runs
gen_redirects.pyto createdocfx.jsonfor each language - Generates language manifest - Creates
metadata/languages.jsonfor runtime language switching - 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--syncto enable full English fallback for missing/outdated translations (useful for local development). - Normalizes DocFX alerts - Runs
normalize-localized-alerts.pyon each non-English language to repair Crowdin-collapsed Note/Tip/etc. alerts before building (see DocFX Alerts and Translations) - Stabilizes heading anchors - Runs
normalize-localized-heading-anchors.pyon each non-English language to inject English-slug bookmark anchors before translated headings, so#anchorcross-references resolve even when the heading text is translated (see Bookmark Links and Translations) - Builds documentation - Runs DocFX for each requested language
- Fixes API docs - Patches xref links in generated API documentation
- Copies API docs - Shares English API docs with localized sites
- Injects SEO tags - Adds hreflang and canonical tags to HTML files
- Generates SWA config - Creates
staticwebapp.config.jsonfor Azure Static Web Apps routing
/
├── 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/
└── ...
- Create
localizedContent/{lang}/content/folder (e.g.,fr/content/) - Add the language entry to
metadata/language-metadata.jsonwith name and nativeName - Add translated
.mdfiles to the content subdirectory - Add a translated
_ui-strings.jsonto the content subdirectory (see Translating UI Strings below). If no translation is provided, an automatic fallback will be generated. - Run
python build-docs.py --allto generate configs and build. Language will be added dynamically to language picker.
Note: English content from
content/is automatically copied tolocalizedContent/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--syncflag.
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.
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:
<a id="model-io" data-loc-xref></a>
## E/S del modeloDocFX 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-
Prefer the bracketed link form
[text](xref:uid#anchor)over the bare@uid#anchorautolink. 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 HTMLnameattributes, 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.<a name="my-heading"></a> ## My Heading
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 languageThe _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.
| Key | English value | Element |
|---|---|---|
aiTranslationWarning |
This content has been translated by AI... |
Warning banner shown on translated pages |
header.nav.pricing |
Pricing |
Header nav link |
header.nav.download |
Download |
Header nav link |
header.nav.learn |
Learn |
Header nav link |
header.nav.resources |
Resources |
Header nav dropdown toggle |
header.nav.blog |
Blog |
Resources dropdown item |
header.nav.newsletter |
Newsletter |
Resources dropdown item |
header.nav.publications |
Publications |
Resources dropdown item |
header.nav.documentation |
Documentation |
Resources dropdown item |
header.nav.supportCommunity |
Support community |
Resources dropdown item |
header.nav.contactUs |
Contact Us |
Header nav link |
header.button1 |
Free trial |
Primary header CTA button |
header.button2 |
Main page |
Secondary header button |
footer.heading |
Ready to get started? |
Footer section heading |
footer.button1 |
Try Tabular Editor 3 |
Footer CTA button |
footer.button2 |
Buy Tabular Editor 3 |
Footer CTA button |
footer.aboutUs |
About us |
Footer left link |
footer.contactUs |
Contact us |
Footer left link |
footer.technicalSupport |
Technical Support |
Footer left link |
footer.privacyPolicy |
Privacy & Cookie policy |
Footer bottom link |
footer.termsConditions |
Terms & Conditions |
Footer bottom link |
footer.licenseTerms |
License terms |
Footer bottom link |
appliesTo |
Applies to: |
"Applies to" label on article metadata |
availableSince |
Available since |
Version availability label (e.g., "Available since 3.5.0") |
availableIn |
Available in |
Version range label (e.g., "Available in 3.5.0–3.8.0") |
inThisArticle |
In this article |
Sidebar table of contents heading |
searchResultsCount |
{count} results for "{query}" |
Search results summary |
searchNoResults |
No results for "{query}" |
No search results message |
tocFilter |
Filter by title |
TOC filter input placeholder |
nextArticle |
Next |
Next article navigation |
prevArticle |
Previous |
Previous article navigation |
themeLight |
Light |
Theme picker option |
themeDark |
Dark |
Theme picker option |
themeAuto |
Auto |
Theme picker option |
changeTheme |
Change theme |
Theme picker label |
copy |
Copy |
Code block copy button |
downloadPdf |
Download PDF |
PDF download button |
search |
Search documentation |
Search input placeholder |
note |
Note |
Alert box heading |
warning |
Warning |
Alert box heading |
tip |
Tip |
Alert box heading |
important |
Important |
Alert box heading |
caution |
Caution |
Alert box heading |
tableOfContents |
Table of Contents |
Mobile TOC offcanvas title |
selectLanguage |
Select language |
Language picker label |
copyCode |
Copy code |
Code block copy button aria-label |