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.
To test your changes locally:
- Make sure DocFX and Python 3.11+ are installed.
- Run
python build-docs.py --servein the root of the project.
The build-docs.py script handles all documentation building tasks including multi-language support.
# 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 |
--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) - 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
TEDoc/
├── build-docs.py # Main build script
├── build_scripts/ # Helper scripts
│ ├── gen_redirects.py # Generates docfx.json configs
│ ├── gen_languages.py # Generates language manifest
│ ├── gen_staticwebapp_config.py
│ ├── inject_seo_tags.py
│ ├── sync-localized-content.py
│ └── normalize-localized-alerts.py # Repairs Crowdin-collapsed DocFX alerts
├── 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), the anchor ID is auto-generated from the heading text. When headings are translated by Crowdin, the anchor changes, breaking bookmark links.
To prevent this, add an <a name="..."></a> tag above any heading that is referenced by a bookmark link:
<a name="my-heading"></a>
## My HeadingCrowdin does not translate HTML name attributes, so the anchor remains stable across all languages. Only add these to headings that are actually linked to — there is no need to add them to every 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 |