Interface Translation
When to Use
When translating UI strings, system messages, form labels, module-provided text — anything wrapped in t() or TranslatableMarkup.
Decision: Translation Source
| If you need... | Use... | Why |
|---|---|---|
| Import official Drupal translations | Update Manager + locale.drupal.org | Maintained by community, covers core and contrib |
| Custom module translations | .po files in module/translations/ | Package translations with module |
| Manual string translation | UI at /admin/config/regional/translate |
Override or add missing translations |
| Export custom translations | Locale export at /admin/config/regional/translate/export |
Share or backup translations |
Pattern: Importing Translations
Automatic import (recommended):
1. Enable Locale and Interface Translation modules
2. Add languages at /admin/config/regional/language
3. Drupal automatically downloads translations from localize.drupal.org
4. Updates check for new translations periodically
Manual .po file import:
1. Go to /admin/config/regional/translate/import
2. Select language
3. Upload .po file
4. Choose import options:
- Treat imported strings as custom (overrides future updates)
- Treat imported strings as non-custom (can be updated)
Programmatic import (via batch API):
use Drupal\locale\Gettext;
$file = new \stdClass();
$file->uri = 'public://translations/es.po';
$file->filename = 'es.po';
$file->langcode = 'es';
$options = [
'langcode' => 'es',
'overwrite_options' => [
'customized' => FALSE,
'not_customized' => TRUE,
],
'customized' => LOCALE_NOT_CUSTOMIZED,
];
// Use Gettext parser for direct import
Gettext::fileToDatabase($file, $options);
Pattern: Custom Module .po Files
Directory structure:
mymodule/
mymodule.info.yml
translations/
mymodule.es.po
mymodule.fr.po
mymodule.info.yml — tell Locale where to find .po files:
name: My Module
type: module
core_version_requirement: ^10 || ^11
'interface translation project': mymodule
'interface translation server pattern': modules/custom/mymodule/translations/%project.%language.po
mymodule.es.po — Gettext format:
msgid ""
msgstr ""
"Project-Id-Version: My Module\n"
"POT-Creation-Date: 2026-02-16\n"
"Language: es\n"
msgid "Submit"
msgstr "Enviar"
msgid "Welcome, @name!"
msgstr "¡Bienvenido, @name!"
msgctxt "Long month name"
msgid "May"
msgstr "Mayo"
Automatic discovery:
- Drupal scans for t() and TranslatableMarkup calls
- Extracts source strings to .pot file
- Translators create .po files with translations
- Locale module imports .po files on module enable or via UI
Pattern: Generating .pot Files
Using Drush:
# Generate .pot from module
drush locale:export --langcode=en --template --types=customized > mymodule.pot
# Or use potx module (contrib)
drush potx module mymodule
Using Translation Template Extractor (Potx) module:
- Install Potx module: composer require drupal/potx
- UI: /admin/config/regional/translate/extract
- Select module, generate .pot file
Common Mistakes
- Forgetting 'interface translation project' in .info.yml → Locale module won't discover your .po files. Must define project name and server pattern
- Using wrong .po file naming → Must be
PROJECT.LANGCODE.po(e.g.,mymodule.es.po). Wrong naming prevents import - Not exporting custom translations → Custom manual translations entered via UI are lost on re-import unless exported first
- Translating user-entered content with t() → NEVER wrap user input in
t(). Security risk (XSS). TranslatableMarkup & t() for details - Hardcoding non-English strings → All source strings in code should be English. Translators translate from English source
See Also
- → TranslatableMarkup & t() — using translation API correctly
- → Translating Custom Modules — complete module translation workflow
- Reference:
core/modules/locale/locale.api.php - Reference: https://www.drupal.org/docs/administering-a-drupal-site/multilingual-guide/translating-site-interfaces
- Reference: https://localize.drupal.org (official translation server)