Skip to content

Translating the Linter

If you would like to help out by translating the Linter there are several ways in which you can help out. The following instructions should help you with translating the Linter.

Using translation helper

Before being able to translate using the translation helper, you will need to make sure that you have setup the Linter for local use as described in the Setup Guide.

Before we can make changes to a translation in the Linter, we will want to know what a translation is and how they work.

What is a Translation?

A translation for the Linter is a language file that contains the string values of the text the Linter uses for logs and the UI in Obsidian. The files for the languages are located under src/lang/locale/. Each language has a file that is named using the 2 letter language abbreviation. If the language is for a specific dialect (i.e. simplified Chinese which or British English), make sure it starts with the 2 letter language code followed by the 2 letter code for the language dialect in question.

An example of a regular translation file with no dialect is en.ts.
An example of a translation file for a dialect is zh-cn.ts.

Translation File Structure

A translation file is a TypeScript file that is structured as a JSON object. That is to say it is a key value mapping with the nesting of values in it. The structures of all translation files are to follow the structure of en.ts which is the source of truth for the keys that exist for use in the Linter. So if you need to add a new value or change the structure of the existing values, you will first need to update them in this file.

The following is the general structure of en.ts:

// English

export default {
  // UI text for the Obsidian commands that the Linter uses
  'commands': {...},

  // logs are general pieces of text to either partially include in the UI or to just put in the developer console.
  'logs': {...},

  'notice-text': {
    'empty-clipboard': 'There is no clipboard content.',
    'characters-added': 'characters added',
    'characters-removed': 'characters removed',
  },

  // settings.ts
  'linter-title': 'Linter',
  'empty-search-results-text': 'No settings match search',

  // lint-confirmation-modal.ts
  'warning-text': 'Warning',
  'file-backup-text': 'Make sure you have backed up your files.',

  // tabs contains tab specific UI text and setting text (tab specific setting text is text not generated by rules)
  'tabs': {
    'names': {
      // tab.ts
      'general': 'General Settings',
      'custom': 'Custom Settings',
      'yaml': 'YAML Settings',
      'heading': 'Heading Settings',
      'content': 'Content Settings',
      'footnote': 'Footnote Settings',
      'spacing': 'Spacing Settings',
      'paste': 'Paste Settings',
      'debug': 'Debug Settings',
    },
    // tab-searcher.ts
    'default-search-bar-text': 'Search all settings',
    'general': {...},
    'debug': {...},
  },

  // options is for specific UI elements that help out with settings
  'options': {
    'custom-command': {...},
    'custom-replace': {...},
  },

  // rules is where you put names and descriptions for rules and rule settings, but not dropdown record values
  'rules': {...},

  // These are the string values in the UI for enum values and thus they do not follow the key format as used above
  // they are also for dropdown records that come from union types
  'enums': {...},
};

As you can see the Linter is broken into the several sections which each have their own uses:

Name Description
commands The portion of the language text to use for Obsidian commands that the Linter creates.
logs The portion of the language text to use for general pieces of text to either partially include in the UI or to just put in the developer console.
notice-text The portion of the language text to use for notices that are used by the Linter to alert the user about something that is not an error.
tabs The portion of the language text to use for tab specific UI text and setting text (tab specific setting text is text not generated by rules).
options The portion of the language text to use for custom Linter components that in general help out with settings. Things like custom commands and custom regex replace have components that have their text listed in this section.
rules The portion of the language text to use for names and descriptions for rules and rule settings, but not dropdown record values.
enums The portion of the language text to use for values in the UI for enum values and thus they do not follow the key format as used above. It is also for dropdown records that come from union types.

How Are Partial Translations Handled?

Any missing keys in a language will be defaulted to its English values which allows for partial translation of a language. This way you can add as many or as few translations to the translation file at any given time.

Adding/Updating Values for an Existing Language

If you would like to add a translation to a language the Linter already has a file for under src/lang/locale, you can add values manually to the desired file or via running npm run translate. If you choose to use the script, you will be able to add a translation value for a specific language, list untranslated keys in a specified language, replace translated value with a new value for a specified key, translate all untranslated keys in a language one at a time

If you choose to do so manually, you will need to copy the structure found in en.ts to the language that you want to add values for.

Translation helper requirements

The translation helper requires that you have already followed the steps to setup npm and have installed its dependencies for this project.

Once the translation(s) has/have been added, the translation(s) should be ready for review. So go on ahead and open a pull request.

Adding a New Language Translation

If you want to add language support for a language, but the file does not already exist, there are a couple of steps that need to be followed for adding that language to the Linter.

1. Create the Translation File

Determine the name for the translation file as described by What is a Translation?. Once you know what the name of the file should be, create a new file for it under src/lang/locale/. You will want to make sure that the new file follows the following format:

// {NAME_OF_LANGUAGE_IN_LANGUAGE_HERE}

export default {};

We will use an example of adding Spanish as the new language. The 2 letter abbreviation for Spanish is es so the new file will be called es.ts.

So we will create a file under es.ts that as follows:

// EspaƱol

export default {};

Note

The name of the language in the comment is the name used to refer to the language of Spanish in Spanish. Doing this helps native speakers better identify the language and dialect that the translation is for.

2. Update Imports in Language Helper

Now that the language file has been created it exists and is ready to be used by the Linter. The only problem is that the Linter does not know that the file exists. So we need to update helpers.ts and add the new file to the list of files it knows about. To do this add the following line to helpers.ts:

import {LANGUAGE_SHORT_CODE} from './locale/{LANGUAGE_SHORT_CODE}';

Then the localeMap in helpers.ts needs to have an entry added in it for the new language:

export const localeMap: { [k: string]: LanguageLocale } = {
  en,
  {LANGUAGE_SHORT_CODE},
  ...
};

Lastly you will need to make sure that you add the proper value to the list of locales to their corresponding file names:

export const localeToFileName: { [k: string]: string} = {
  'en': 'en',
  '{LANGUAGE_SHORT_CODE}': '{LANGUAGE_SHORT_CODE}',
  ...
}

Why add values to locale to file name object?

This is is done because it allows the UTs to validate that there is a unique file that exists for each locale that the Linter users.

In the case of adding Spanish, we would make the following changes:

import es from './locale/es'; // import new language into helper file
...
export const localeMap: { [k: string]: LanguageLocale } = {
  en,
  es, // add new language to locale mapping
  ...
};

export const localeToFileName: { [k: string]: string} = {
  'en': 'en',
  'es': 'es', // add new language locale to file name mapping
  ...
}

Special Considerations for Locale Map

In some cases, the language name of the file and the language name of the Obsidian locale used are different. In these cases, you will want to make sure to map the Obsidian locale to the new language:

export const localeMap: { [k: string]: LanguageLocale } = {
  en,
  es,
  'pt-BR': ptBR, // special mapping of Obsidian locale to language
  ...
  'zh-TW': zhTW, // special mapping of Obsidian locale to language
  'zh': zhCN, // special mapping of Obsidian locale to language
};

export const localeToFileName: { [k: string]: string} = {
  'en': 'en',
  'es': 'es',
  'pt-BR': 'pt-br', // special mapping of Obsidian locale to language file name
  ...
  'zh-TW': 'zh-tw', // special mapping of Obsidian locale to language file name
  'zh': 'zh-cn', // special mapping of Obsidian locale to language file name
};

3. Add Translations to File

Now that the translation file has been created and the language helper knows about the new language and what Obsidian locale to use it for, the next thing to do is add translations to the newly created file.

4. Open a Pull Request

Once the translation(s) has/have been added, the new language addition should be ready for review. So go on ahead and open a pull request.