Meta description: Found a mysterious .ts file in your Django project? Use terminal commands to identify it fast and route it into the right i18n workflow.
You pull a branch during a localization sprint, run git status, and there it is: labels.ts.
You open it expecting gettext content. Instead you get one of three bad surprises. Binary garbage. Frontend code. Or XML that looks like translations, but not the kind Django knows what to do with.
That's the problem with the file format TS. The extension is overloaded. It can mean TypeScript source, MPEG transport stream video, or a Qt translation source file, and a lot of guides only explain the video case. That leaves you guessing at the worst time, usually when your release already depends on getting strings into locale/<lang>_<REGION>/LC_MESSAGES/django.po and compiled before deploy. The ambiguity across those three file types is documented in this overview of TS file meanings from this video explanation.
The fix is to stop guessing. Identify the file from the terminal first, then route it to the right toolchain. That saves you from trying to “translate” a video container, feeding source code into a PO workflow, or ignoring a Qt translation asset that matters.
That Mysterious .ts File in Your Project
A .ts file showing up in a Django repo usually means one of three things.
Frontend code got added by a React, Vue, or build-tool change. A media artifact slipped in from a streaming or upload workflow. Or someone from another team exported translations from a Qt-based tool and checked them in next to your app code.
Only one of those belongs anywhere near your Django i18n pipeline.
I've seen teams lose time because they treat every .ts file like text they can convert later. That breaks fast. A TypeScript file is source code. An MPEG transport stream file is a media container. A Qt .ts file is the one case that is a translation asset, but it still isn't usable by Django as-is.
Practical rule: never decide what a
.tsfile is from the filename alone. Decide from its contents.
When you're working on localization, the confusion gets worse because .po and Qt .ts both contain translatable strings. They solve similar problems with different structures. If you don't identify the file first, you'll waste time writing the wrong conversion step, or worse, commit junk to your locale tree.
How to Identify Your .ts File Type
The first move is always the same. Ask the operating system what it thinks the file is, then inspect the opening lines.

Start with file and head
Run these commands from your project root:
file path/to/unknown.ts
head -n 20 path/to/unknown.ts
On macOS and Linux, file is usually enough to separate binary media from text. head confirms the text-based cases.
Here's the pattern I use:
file assets/labels.ts
head -n 10 assets/labels.ts
Likely outputs and what they mean:
file output pattern |
head output pattern |
What you have | What to do next |
|---|---|---|---|
| MPEG transport stream | unreadable or binary-looking data | Video container | Convert, move, or ignore |
| ASCII text / UTF-8 text | import, export, interface, class |
TypeScript source | Keep out of Django i18n |
| XML document / text | <?xml version="1.0"?> and <!DOCTYPE TS> |
Qt translation file | Convert into .po |
| UTF-8 text | lines beginning with @, then @data |
Time-series dataset format | Handle as ML data, not i18n |
That fourth case catches people off guard. The .ts extension is also used by a formal time-series data format documented by sktime's .ts specification. It's a UTF-8 encoded format with a description block, metadata lines starting with @, and a dataset block marked by @data. It supports dataset metadata such as @problemName, @missing, @timestamps, @serieslength, and labels for regression or classification. That's useful in machine learning workflows, but it has nothing to do with Django translations.
Quick signatures you can trust
You don't need a long forensic process. You need a fast one.
- If you see
<!DOCTYPE TS>, treat it as a Qt translation file. - If you see
importand typed function signatures, treat it as TypeScript. - If
headis noise andfilesays MPEG, stop opening it in your editor. - If metadata lines start with
@, you're probably in time-series data territory.
Don't use file icons, IDE syntax highlighting, or somebody's commit message as your source of truth.
For teams, this is worth codifying in CI. A tiny shell check can fail the build when an unexpected .ts lands inside locale/ or a release package.
find . -name "*.ts" -print
That command won't identify the type on its own, but it gives you the audit list. From there, run file on anything suspicious before you decide whether it belongs in your repo, your asset pipeline, or your translation workflow.
Handling MPEG Transport Stream Video Files
If file says MPEG transport stream, you're dealing with media, not text.

MPEG-TS is a digital container used in broadcast ecosystems including DVB, ATSC, and IPTV, and it's common in streaming and Blu-ray workflows, as described in this overview of MPEG transport stream usage. In a Django codebase, that usually means one of two things. You have a real media asset from a video workflow, or you have a temporary artifact that never should have been committed.
What works
If you need the video, convert it to something friendlier for your app or review process:
ffmpeg -i input.ts -c copy output.mp4
That remuxes the streams into MP4 without re-encoding. If the output has compatibility issues, fall back to a full transcode:
ffmpeg -i input.ts -c:v libx264 -c:a aac output.mp4
If your app handles HLS or webcam pipelines, you'll run into TS segments more often. For that side of the stack, this guide to HLS encoding for live webcams is useful because it explains why these files keep showing up in streaming systems even when your product team only thinks in terms of “video uploads.”
What does not
Don't commit transport stream artifacts to the repo unless they are versioned test fixtures and you've agreed on that explicitly.
Don't put them in locale/, don't pass them through gettext tooling, and don't assume every downstream system will accept them. A more practical problem is pipeline breakage. Real-world examples show .ts files can cause packaging or upload failures in automated systems, including LMS and validator workflows, as discussed in this example of pipeline issues with .ts files.
A decent default .gitignore for media-heavy repos looks like this:
*.ts
!src/**/*.ts
!frontend/**/*.ts
That pattern is intentionally narrow. Blanket-ignore every .ts file and you'll hide TypeScript source too.
A related format question comes up a lot when teams also ship captions. If that's your case, this write-up on the subtitle file format landscape is worth keeping nearby because subtitle workflows and transport stream workflows often get mixed together in product discussions even though they belong to different toolchains.
Here's a quick visual refresher on media-side handling:
Managing TypeScript Source Code Files
If the file starts with import, export, type, interface, or a typed function signature, it's TypeScript. Treat it as code.
That sounds obvious, but I've seen backend teams drag .ts files into translation planning because they contain UI strings. The file itself is not the translation artifact. It's the place where strings originate. Your frontend i18n tool should extract those strings into JSON, ICU message catalogs, or whatever your JS stack uses.
Keep Django gettext in its lane
Django's makemessages is for Django templates and Python code. It is not your frontend extractor.
If your repo mixes Django and a TypeScript frontend, keep responsibilities clear:
- Django strings live in Python and templates, then land in
.po. - Frontend strings live in
.tsor.tsx, then go through frontend extraction. - Shared copy needs an explicit contract. Don't fake one by pointing everything at gettext.
Here's the backend side you want to preserve:
django-admin makemessages --locale=fr
python manage.py compilemessages
And here's a normal Django model label pattern that belongs in that workflow:
from django.db import models
from django.utils.translation import gettext_lazy as _
class Invoice(models.Model):
customer_name = models.CharField(_("customer name"), max_length=255)
If your team is tempted to “translate the .ts file directly,” stop there. That usually means your frontend extraction is missing or inconsistent. This article on translating computer code is a useful sanity check because it draws the line between localizing user-facing strings and editing source files that happen to contain them.
Code files can contain translatable content. They are still code files.
Working with Qt Linguist Translation Files
The Qt case is the one that matters for Django localization.
If head shows XML and <!DOCTYPE TS>, you've found a Qt Linguist translation source file. It isn't TypeScript, and it isn't video. It's a translation asset from the Qt ecosystem.

What it looks like
A typical file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR">
<context>
<name>MainWindow</name>
<message>
<source>Hello, %(name)s</source>
<translation>Bonjour, %(name)s</translation>
</message>
<message>
<source>Save changes</source>
<translation></translation>
</message>
</context>
</TS>
The parts to care about are:
<context>groups messages by UI area or class.<name>names that context.<source>holds the original string.<translation>holds the target string.- Attributes on the root element can tell you the target locale.
That structure is close enough to gettext that conversion is practical. It is not close enough that Django can use it directly.
Why teams miss this case
A lot of “open TS file” guides stop at media playback. That's why localization teams get stuck when a designer, desktop team, or plugin export hands them a Qt .ts file. It looks translation-related, but it doesn't fit the Django path you already know.
The wrong response is to hand-edit XML and copy strings manually into .po. That loses context fast, increases review noise, and makes repeated imports miserable.
The better response is to treat Qt .ts as an upstream format. Convert it once, preserve placeholders, and bring it into the same Git-reviewed flow as the rest of your gettext catalogs.
The packet-based 188-byte structure you may associate with transport-stream
.tsfiles belongs to the media format, not the Qt one. That detail matters because people often search “TS format” and get the wrong answer first. The packet size and error-tolerant delivery behavior are described in this overview of TS packet structure.
A quick inspection habit
Before converting, scan the file for placeholder patterns and repeated contexts:
grep -n "<source>" locale-designer-export.ts | head
grep -n "%(" locale-designer-export.ts
grep -n "{0}" locale-designer-export.ts
You're checking whether the strings line up with your Django placeholder conventions or whether you'll need extra review after conversion.
If the file is full of UI labels that already exist in your Django app, decide who owns the source of truth before importing anything. Blindly merging Qt-generated messages into django.po can create duplicate copy with slightly different wording.
A Practical Workflow for Qt .ts and Django i18n
Once you know the file is Qt translation XML, the job is mechanical. Convert it into gettext, put it where Django expects it, review it, compile it, and wire that into CI.

Convert first, then normalize placement
I prefer translate-toolkit for this kind of bridge work.
Install it in your environment:
python -m pip install translate-toolkit
Then convert Qt TS into XLIFF, and XLIFF into PO:
ts2xlf designer-export.ts designer-export.xlf
xlf2po designer-export.xlf django.po
Put the result where Django expects it:
mkdir -p locale/fr_FR/LC_MESSAGES
mv django.po locale/fr_FR/LC_MESSAGES/django.po
Then compile:
python manage.py compilemessages
If your app uses fr rather than fr_FR, align the directory name with your project's language setup before you commit. Don't keep both unless you mean to.
Review the PO before you trust it
A converted file still needs a fast review pass.
Check these items:
- Placeholder integrity. Look for
%(name)s,%s, and{0}. - HTML safety. If source strings contain tags, keep them intact.
- Plural handling. Qt and gettext don't model everything the same way.
- Duplicate msgids. Imported strings can collide with existing Django copy.
Here's the kind of output you want to inspect after conversion:
msgid "Hello, %(name)s"
msgstr "Bonjour, %(name)s"
msgid "Save changes"
msgstr ""
And if the string belongs in a Django template or Python callsite later, it should still map cleanly to normal gettext usage:
<h1>{% translate "Save changes" %}</h1>
Make CI reject ambiguous leftovers
One operational rule helps a lot. After conversion, don't leave the original Qt .ts file sitting in a package or deploy artifact unless you need it there.
Unexpected .ts files can break downstream systems. There are real cases where packaging validators or LMS-style upload pipelines reject artifacts that contain .ts files because of policy or file-type restrictions. That's documented in the earlier pipeline example, and it's the reason I add an explicit check in CI for stray extension-based artifacts after packaging.
A minimal check might be:
find build -name "*.ts" -print
If that prints anything in a Django deploy artifact, inspect it before shipping.
For teams that already keep translations in version control and want to automate filling missing msgstr values after conversion, one option is TranslateBot's guide on working with translations. The package itself fits into the normal Django flow by translating .po files from a management command instead of sending you through a separate portal. That's useful once the Qt file has already been turned into gettext.
Convert foreign translation formats at the boundary. Inside your Django repo, keep one canonical translation format.
Your Checklist for the Next .ts File
When the next .ts file lands in your project, don't guess.
- Identify it first: run
file unknown.tsandhead -n 20 unknown.ts. - If it's MPEG-TS: convert with
ffmpegor keep it out of the repo. - If it's TypeScript: leave it in the frontend code path, not Django gettext.
- If it's Qt XML: convert it into
.po, move it tolocale/<lang>_<REGION>/LC_MESSAGES/django.po, then runcompilemessages. - If it shows up in CI artifacts: audit it before deploy, because downstream validators may reject it.
That workflow is repeatable. More important, it keeps your localization pipeline boring, which is what you want right before a release.
If your team already uses Django gettext and you want to automate the last step after makemessages or a Qt-to-PO conversion, TranslateBot is one option. It runs inside your project, writes back to .po files, and keeps the reviewable Git-based workflow most Django teams already prefer.