Good day, for the past 3 weeks I have been busy translating the popular documentation platform known as MDN web docs into the Spanish version, to help Spanish-speaking readers as it had been outdated for years with respect to the English version.
This has been part of my open-source journey. In this article, I want to talk about the process, to enable future contributors to seamlessly contribute one day.
Here are the rules to follow when trying to do the Spanish-translation work. Everything that I am going to write here is already mentioned inside of the rules.
Here is one rule to just bear in mind
is this text something a human reads (translate it) or something the code/interpreter looks up by exact name (leave it untouched)
File Paths
This is the most important one; File paths (bolded or inline) are never translated. It stays the same. Below, we will discuss the only thing that changes, which is en-US to es
English: docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Spanish: docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Front-matter
- Final front-matter should only contain:
title,slug,l10n.sourceCommit(plusshort-titlewhen the English file has one). - Remove
page-type,sidebar,original_slug. -
title(andshort-title): translate to Spanish. -
slug: never translate — left exactly as in English. -
l10n.sourceCommit: set to the exact SHA given for that English source commit.
Links
External links: These are links point to external web pages and they should be left as is. For example: https://validator.w3.org/ or https://developer.flutterwave.com/docs/testing
Internal links: All the internal links that point to pages with in the MDN web docs website have to be changed from /en-US/ to /es/.
For example:
Original (English source):
/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
This has to be changed to point to a spanish file now.
/es/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Link Text: (ONLY the visible label) gets translated when it's an internal MDN link.
For example
Original (English source):
[Object basics](/en-US/docs/Learn_web_development/Core/Scripting/Object_basics)
We have to translate The link text "Object basics" to "Fundamentos de objetos"
Translated (Spanish):
[Fundamentos de objetos](/es/docs/Learn_web_development/Core/Scripting/Object_basics)
Anchors: when you have an anchor like this, #heading_about_a_paragraph. Two things are involved:
- You need to check if that section that the anchor is referring to exists.
- If it exists, then you should translate it to Spanish
#encabezado_sobre_un_párrafo
Bold / italics
- Keep the bold/italic markers in the same place, only translate the text content.
- Text content that were bold or italics in English stay bold or italics in Spanish.
Original (English source):
_this is supposed to be italic_ and **this is supposed to be bold**
Translated (Spanish):
_esto se supone que debe estar en cursiva_ and **esto se supone que debe estar en negrita**
Image(s)
Never copy images into the files/es/ folder because the build pulls them from the English folder automatically. Just leave the image file path as is
Only the alt text (the [...] part) gets translated; the filename or file path stays exactly as-is.
For example:
Original (English source)

Translated (Spanish)

Callouts
In GitHub markdown, callouts such as
[!NOTE]and[!WARNING]stay untranslated because translating them breaks the styled box.ONLY the user-facing text content contained within the box gets translated.
For example,
Original (English source)
>[!NOTE]
> See also the [glossary entry on API](/en-US/docs/Glossary/API) for more information.
Translated (Spanish)
>[!NOTE]
> Consulta también la [entrada del glosario sobre API](/es/docs/Glossary/API) para más información.
Notice: [!NOTE] remains untouched
Code
Plain Code blocks: These are code blocks that contain only literal data: no comments, no prose mixed in so there's nothing to translate at all.
https://example.com/articles/
https://example.com/articles/23
https://example.com/articles/?page=2
This stays exactly the same in the Spanish file — same URLs, same formatting, no changes whatsoever. There's no comment explaining anything and no prose sentence inside it.
name
age
city
greet
These are actual JavaScript property names being listed for the reader to look at, not descriptive text, so translating "city" to "ciudad" or "age" to "edad" would misrepresent what the code actually contains.
HTML table structure:
Table tags (<table>, <tr>, <th>, etc.) stay exactly the same, only the visible text inside is translated.
For example:
Original (English source):
<table class="properties">
<tbody>
<tr>
<th scope="row">Prerequisites</th>
<td>
A basic understanding of <a href="/en-US/docs/Learn_web_development/Core/Scripting">JavaScript basics</a>.
</td>
</tr>
<tr>
<th scope="row">Objective</th>
<td>To understand JavaScript's prototype-based inheritance model.</td>
</tr>
</tbody>
</table>
Translated (Spanish):
<table class="properties">
<tbody>
<tr>
<th scope="row">Prerrequisitos</th>
<td>
Un entendimiento básico de <a href="/es/docs/Learn_web_development/Core/Scripting">los fundamentos de JavaScript</a>.
</td>
</tr>
<tr>
<th scope="row">Objetivo</th>
<td>Comprender el modelo de herencia basado en prototipos de JavaScript.</td>
</tr>
</tbody>
</table>
Kumascript macros {{...}}
Macro names and their arguments (page paths) are never translated. The macro names such as (
NextMenu,PreviousMenuNext) stay in English because they're internal MDN template functions, not prose.{{glossary("term")}}: if the English term doesn't naturally read well in Spanish, add a second argument with the translated display text —{{glossary("term", "término")}}. If the term is used as-is in Spanish (e.g.HTTP,JavaScript,Python), leave it with no second argument.Navigation macros (
{{NextMenu(...)}},{{PreviousMenuNext(...)}}) always left fully untouched whether in English or Spanish.
{{NextMenu("Learn_web_development/Core/Scripting/Object_prototypes", "Learn_web_development/Core/Scripting")}}
{{PreviousMenuNext("Learn_web_development/Core/Scripting/Object_prototypes", "Learn_web_development/Core/Scripting/Object_building_practice", "Learn_web_development/Core/Scripting")}}
Headings
- Translate headings to match the reference heading list given in the issue, using consistent verb style (gerund/imperative) across the doc. When you finish translating, review your work and make sure you have same headings, same order, same structure as the English source and Nothing is left untranslated
English (Original Source)
## Summary
## Basics: anatomy of a URL
## Scheme
## Authority
## Path to resource
## Parameters
## Anchor
## How to use URLs
## Absolute URLs vs. relative URLs
## URL usernames and passwords
## Semantic URLs
## See also
Translated (Spanish)
## Resumen
## Conceptos básicos: anatomía de una URL
## Esquema
## Autoridad
## Ruta al recurso
## Parámetros
## Ancla
## Cómo utilizar las URL
## URL absolutas frente a URL relativas
## Nombres de usuario y contraseñas en las URL
## URL semánticas
## Véase también
- Watch for accented characters affecting auto-generated anchor targets (e.g. "Validación" becomes anchor
validación_html, not validacion_html) because a missing accent in a link breaks the anchor.
Link: /es/docs/Learn_web_development/Core/Forms/Form_validation#validación_html
For example;
English (Original source)
## Validation HTML
Here's a small but easy-to-miss detail about headings and links. When MDN builds a page, it auto-generates an anchor ID for every heading by lowercasing the text and replacing spaces with underscores. The catch is that it keeps accented characters exactly as they are.
Translated Spanish
## Validación HTML
Aquí hay un detalle pequeño pero fácil de pasar por alto sobre los encabezados y los enlaces. Cuando MDN construye una página, genera automáticamente un ancla para cada encabezado convirtiendo el texto a minúsculas y reemplazando los espacios con guiones bajos. La particularidad es que mantiene los caracteres acentuados exactamente como están.
Comments
Comments inside of Code blocks
This has two (2) cases:
- A comment that is explaining something to the user (reader) should be translated.
- A comment that does not add any additional useful information should be discarded.
Useful comment(s)
A comment explaining something (translate it):
This comment is real prose explaining why the line exists or what it does. A Spanish-speaking reader benefits from having this translated
English (Original Source)
# Number of visits to this view, as counted in the session
num_visits = request.session.get('num_visits', 0)
Translated (Spanish)
# Número de visitas a esta vista, contado en la sesión
num_visits = request.session.get('num_visits', 0)
Throwaway comment(s)
Example 1
INSTALLED_APPS = [
# ...
'django.contrib.sessions',
]
Here, # ... isn't explaining anything — it's just a visual placeholder telling the reader "there's more code here we're not showing." There's no real content to translate; translating ... doesn't mean anything different in Spanish. Best left as-is.
Example 2
const wheels = 'alloy'; // or comment
This kind of comment is minor, vague, and doesn't add much context either way, so it's genuinely a coin-flip: leave it in English since it's a throwaway aside, or translate it for full consistency with the rest of the document.
Keywords, Variables, Functions:
DO NOT TRANSLATE ANY OF THESE
Variable names — e.g. numOfVisits, myCar, count
Function/method names — e.g. getPrototypeOf(), index()
Class names — e.g. Person, Author
Property/dictionary keys — e.g. city, wheels, my_car['wheels']
Exception names — e.g. KeyError
Boolean values (true, false): remain untranslated in both English and Spanish
English (Original Source)
// Uses 'toString()' by default, returns true
Translated (Spanish)
// Usa 'toString()' por defecto, devuelve true
Methods
Method names mentioned inside comments stay untouched.
English (Original Source)
// Calls 'getPrototypeOf()' to find the parent
Translated (Spanish)
// Llama a 'getPrototypeOf()' para encontrar al padre
Console.log()
There are two (2) incidents here:
- For literal strings printed to the user, you should translate it. An example is
English (Original Source):
console.log("Greetings from Madrid");
Translated Spanish:
console.log("Saludos desde Madrid");
- short/generic literal, here it's a judgment call, and the practical approach is to flag it for the reviewer
Example:
function greet() {
console.log("hello!");
}
Pluralization
If translating a word breaks a language feature (e.g. pluralization), adapt the word so the mechanism still works (e.g. "time" becomes "vece").
"time" plural form is "times" in English, but the literal spanish translation would be wrong (broken) spanish.
if we were to follow a literal translation of "time" into spanish "vez" literal plural translation "vezs" (which is wrong spanish)
"vez" correct plural form in spanish is "vece".
num_visits = 3, Here, pluralize looks at num_visits and, if it's more than 1, appends an "s" to whatever comes right before the template tag — turning "time" into "times".
English (Original Source)
You have visited this page {{ num_visits }} time{{ num_visits|pluralize }}.
Adapted translation (mechanism still works):
Has visitado esta página {{ num_visits }} vece{{ num_visits|pluralize }}.
More Information
- Run Prettier on the finished file, when you are done translating.
npx prettier --write files/es/learn_web_development/core/scripting/object_prototypes/index.md
- Check for invisible/zero-width characters (e.g. using a script scanning for
U+200B,U+200C,U+200D,U+FEFF)
import unicodedata
import sys
INVISIBLE_CHARS = (0x200B, 0x200C, 0x200D, 0xFEFF)
def check_file(path):
with open(path, encoding="utf-8") as f:
for line_number, line in enumerate(f, 1):
found = [
unicodedata.name(char, "?")
for char in line
if ord(char) in INVISIBLE_CHARS
]
if found:
print(line_number, found, repr(line[:40]))
if __name__ == "__main__":
check_file(sys.argv[1])
Top comments (0)