LanguageTool

Development

This page has everything you need to know to teach LanguageTool new error detection rules, plus more. You don't even have to be a programmer for that.

The three-minute introduction

This section tells you in a nutshell how to write your own LanguageTool rules for detecting errors:

  1. Download the stand-alone version of LanguageTool from the homepage and unzip it.
  2. Open org/languagetool/rules/en/grammar.xml in your preferred text editor or in an XML editor.
  3. Search for name="Possible Typos" (it's quite at the top) and copy and paste this snippet just after that category element:
    <rule id="EXAMPLE_RULE" name="My example rule">
        <pattern>
          <token>foo</token>
          <token>bar</token>
        </pattern>
        <message>Did you mean <suggestion>bicycle</suggestion>?</message>
        <example type="incorrect">My <marker>foo bar</marker> is broken.</example>
        <example type="correct">My bicycle is broken.</example>
    </rule>
  4. Run languagetool-standalone.jar by clicking it or by calling java -jar languagetool-standalone.jar in your command line.
  5. Select English as the text language and type something like "A foo bar tour in London", then start text checking.
  6. LanguageTool will now check your text and suggest "bicycle" as a replacement for "foo bar", because that's what the rule which we just added says.

That's it! You have just added a new rule. Keep on reading to get a grasp on what the elements of a rule mean and how to build more complex rules or use the rule creator to build simple rules.

Help wanted!

We're looking for people who support us writing new rules so LanguageTool can detect more errors. Also see the list of supported languages.

How can you help?

  1. Read this page (some features described here are quite advanced, so you won't need everything)
  2. Start writing rules for the error you'd like LanguageTool to detect
  3. See our wiki for more tips and tricks
  4. Post your rules to our mailing list so we can include them in LanguageTool

If your language isn't supported yet, you can add it by following the documentation in our wiki.

Source code checkout (Java developers only)

If you are a Java developer and you want to extend LanguageTool or if you want to use the latest development version, check out LanguageTool with Subversion:

svn checkout http://svn.code.sf.net/p/languagetool/code/trunk/languagetool languagetool

Alternatively, you can get the code from github, where it is mirrored (Sorry, the mirror is currently not up-to-date - May 2013):

git clone https://github.com/danielnaber/languagetool-mirror.git

You can then build the code with mvn clean package or just run the tests with mvn clean test. Maven's default memory settings are often too low, so you will probably need to set your environment variable MAVEN_OPTS to:

-Xmx512m -XX:MaxPermSize=256m

After the build, the LibreOffice/OpenOffice extension can be found in languagetool-office-extension/target, the stand-alone version in languagetool-standalone/target. Please also see the Usage page.

Language checking process

This is what LanguageTool does when it analyzes a text for errors:

  1. The text is split into sentences
  2. Each sentence is split into words, so called tokens
  3. Each word is assigned its part-of-speech tag(s) (e.g. cars = plural noun, talked = simple past verb)
  4. The analyzed text is then matched against the built-in rules and against the rules loaded from the grammar.xml file

The most important thing you need to keep in mind that LanguageTool's rules describe what errors look like, not what correct sentences look like (this is the opposite of how you learn a new language).

Adding new XML rules

Most rules are contained in rules/xx/grammar.xml, whereas xx is a language code like en or de. A rule is basically a pattern which shows an error message to the user if the pattern matches. A pattern can address words or part-of-speech tags. Here are some examples of patterns that can be used in that file:
  • <token>think</token>
    matches the word think
  • <token>think</token>
    <token>about</token>
    Matches the phrase think about - as the text is split into words, you need to list each word separately as a token. This will not work:
    <token>think about</token>
  • <token regexp="yes">think|say</token>
    matches the regular expression think|say, i.e. the word think or say. You can write simple rules without knowing regular expressions, but if you want to learn more about them you can try this tutorial.
  • <token postag="VB" />
    <token>house</token>
    matches a base form verb followed by the word house. See resource/en/tagset.txt for a list of possible English part-of-speech tags.
  • <token>cause</token>
    <token regexp="yes" negate="yes">and|to</token>
    matches the word cause followed by any word that is not and or to
  • <token postag="SENT_START" />
    <token>foobar</token>
    matches the word foobar only at the beginning of a sentence. The corresponding postag for the end of a sentence is SENT_END.

A pattern's tokens are matched case-insensitively by default. This can be changed for the whole pattern by setting the pattern's case_sensitive attribute to yes.

Alternatively, case-sensitive matching can be turned on for single tokens by using (?-i) in regular expressions (ex: <token regexp="yes">(?-i)Bill</token> will match "Bill" but not "bill").

A simple example

Here's an example of a complete rule that marks "bed English", "bat attitude" etc as an error:

<rule id="BED_ENGLISH" name="Possible typo &apos;bed/bat(bad) English/...&apos;">
    <pattern>
      <marker>
        <token regexp="yes">bed|bat</token>
      </marker>
      <token regexp="yes">English|attitude</token>
    </pattern>
    <message>Did you mean <suggestion>bad</suggestion>?</message>
    <url>http://some-server.org/the-bed-bad-error</url>
    <example type="correct">Sorry for my <marker>bad</marker> English.</example>
    <example correction="bad" type="incorrect">Sorry for my <marker>bed</marker> English.</example>
</rule>

The basic elements of a rule

A short description of the elements and their attributes:

  • element rule, attribute id: An internal identifier used to address this rule. This must be unique.
  • element rule, attribute name: The text displayed in the configuration.
  • element pattern, attributes mark_from and mark_to (LanguageTool <= 1.7): What part of the original text should be marked as an error. The default, mark_from="0" and mark_to="0", means to mark the complete matching token. For example, if the pattern contains three token elements that match the input text, those three matching words will be marked in the text. mark_to="-1" in the example above means that the last token of the match will not be marked.
  • element pattern, sub element marker (LanguageTool >= 1.8): What part of the original text should be marked as an error. If all tokens are part of the error you can omit this element.
  • element token, attribute regexp: interpret the given token as a regular expression
  • element message: The text displayed to the user if this rule matches. Use sub-element suggestion to suggest a possible replacement that corrects the error. Since version 1.8, it is possible to conditionally suppress parts of suggestions if they are misspelled (for this, you need to use element match with attribute suppress_misspelled set to yes). You can even suppress the whole rule from being matched if you use the same attribute for any suggestion element. Note: the tagger of the given language is used to make it work, so if you don't have a tagger yet, you cannot use this feature.
  • element url (optional, since LanguageTool 1.7): An URL to a page that explains the rule leading to the error in more detail.
  • element example: At least two examples with one correct and one incorrect sentence. The sentence with the attribute type="incorrect" is supposed to be matched by this rule. The position of the error must be marked up with the sub-element marker. You can use the optional correction attribute to make the test also check whether the correction suggested by LanguageTool is what you expect. These sentences are used by the automatic test cases that can be run using sh testrules.sh (on Linux), testrules.bat (on Windows), or mvn clean test (for Java developers).

Testing rules

The LanguageTool user interface (languagetool-standalone.jar) needs to be restarted if you have changed the grammar.xml file. Testing rules is faster with our embedded test case feature: just call sh testrules.sh en on Linux or testrules.bat en on Windows, using your language code instead of en.

This will test your rule with its example sentences: the incorrect sentence is supposed to be detected by your rule, while the correct sentence is not supposed to give an error. If that is not the case you will get a message. In that case, either your rule or your example sentences are not quite right yet.

Using testrules.sh/bat is not only much faster than manually starting the user interface over and over again, it will always test all rules, so we recommend you use that during rule development.

Inflection

The inflected attribute of the token element is used to match not only the given form but also all of its inflected forms. For example <token inflected="yes">bicycle</token> will match bicycle, bicycles, bicycling etc.

Grouping rules

Sometimes it requires more than one rule to find all occurrences of an error. You can put all those rules in one rulegroup element. The rulegroup's id and name attribute will be use for all the rules of that group. Starting with LanguageTool 1.8, overlapping matches for rules in the same rulegroup are filtered out to avoid duplicate matches for the same error.

Categories

The rules are best put into categories that describe their purpose, and allow to enable or disable a number of rules at the same time. When creating a category, you can use the type attribute to describe the type of the error according to the Quality Issue Type from the W3 Internationalization Tag Set. This will make integration of LT with other tools easier.

Turning rules off by default

Some rules can be optional, useful only in specific registers, or very sensitive. You can turn them off by default by using an attribute default="off". The user can turn the rule on/off in the Options dialog box, and this setting is being saved in the configuration file.

Skip

The skip attribute of the token element is used in two situations:

  1. Simulate a simple chunker for languages with flexible word order, e.g., for matching errors of rection; we could for example skip possible adverbs in some rule. skip="1" works exactly as two rules, i.e.

    <token skip="1">A</token>
    <token>B</token>

    is equivalent to the pair of rules:

    <token>A</token>
    <token/>
    <token>B</token>

    <token>A</token>
    <token>B</token>

    Using negative value, we can match until the B is found, no matter how many tokens are skipped. This cannot be easily encoded using empty tokens as above because the sentence could be of any length.

  2. Match coordinated words, for example to match "both ... as well as" we could write:

    <token skip="-1">both<exception scope="next">and</exception></token>
    <token>as</token>
    <token>well</token>
    <token>as</token>

    Here the exception is applied only to the skipped tokens.

    The scope attribute of the exception is used to make exception valid only for the token the exception is specified (scope="current") or for skipped tokens (scope="next"). Default behavior is scope="current". Using scopes is useful where several different exceptions should be applied to avoid false alarms. In some cases, it's useful to use scope="previous" in rules that already have skip="-1". This way, you can set an exception against a single token that immediately precedes the matched token. For example, we want to match "tak" after "jak" which is not preceded by a comma:

    <token>tak</token>
    <token skip="-1">jak</token>
    <token>tak<exception scope="previous">,</exception></token>

    In this case, the rule excludes all sentences, where there is a comma before "tak". Note that it's very hard to make such an exclusion otherwise.

Variables

In XML rules, you can refer to previously matched tokens in the pattern. For example:

<pattern>
 <token regexp="yes" skip="-1">ani|ni|i|lub|albo|czy|oraz<exception scope="next">,</exception></token>
 <token><match no="0"/></token>
</pattern>

This rule matches sequences like ani... ani, ni... ni, i... i but you don't have to write all these cases explicitly. The first match (matches are numbered from zero, so it's <match no="0"/>) is automatically inserted into the second token. Note that this rule will match sentences like:

Nie kupiłem ani gruszek ani jabłek. Kupię to lub to lub tamto.

A similar mechanism could be used in suggestions, however there are more features, and tokens are numbered from 1 (for compatibility with the older notation \1 for the first matched token). For example:

<suggestion><match no="1"/></suggestion>

A more complicated example:

<pattern>
<token regexp="yes">^(\p{Lu}{2}+[i]*\p{Lu}+[\p{L}&amp;
&amp;[^\p{Lu}]]{1,4}+)</token>
</pattern>
<message>Prawdopodobny błąd zapisu odmiany;
  skrótowce odmieniamy z dywizem:
  <suggestion><match no="1" regexp_match="^(\p{Lu}{2}+[i]*\p{Lu}+)([\p{L}&amp;
&amp;[^\p{Lu}]]{1,4}+)"
regexp_replace="$1-$2"/>
</suggestion></message>

This rule matches Polish inflected acronyms such as "SMSem" that should be written with a hyphen: "SMS-em". So the acronym is matched with a complicated regular expression, and the match replaces the match using Java regular expression notation. Basically, the regular expression only shows two parts and inserts a hyphen between them.

For some languages (currently Polish, English, Catalan, Spanish, Galician, Dutch, Romanian, Slovak and Russian), element <match/> can be used to insert an inflected matched token (or another word with a specified part of speech tag). For example:

<pattern>
 <token regexp="yes">has|have</token>
 <marker>
   <token postag="VBD|VBP|VB" postag_regexp="yes">
     <exception postag="VBN|NN:U.*|JJ.*|RB" postag_regexp="yes"/>
   </token>
 </marker>
 <token><exception postag="VBG"/></token>
</pattern>
<message>
  Possible agreement error -- use past participle here:
  <suggestion><match no="2" postag="VBN"/></suggestion>.
</message>

The above rule takes the second verb with a POS tag "VBN", "VBP" or "VB" and displays its form with a POS tag "VBN" in the suggestion. You can also specify POS tags using regular expressions (postag_regexp="yes") and replace POS tags – just like in the above example with acronyms. This is useful for large and complicated tagsets (for many examples, see Polish rule file: rules/pl/grammar.xml).

Sometimes the rule should change the case of the matched word. For this purpose, you can use case_conversion attribute values: startlower, startupper, allupper and alllower.

Another useful thing is that <match> can refer to a token, but apply its POS to another word. This is useful for suggesting another word with the same part of speech. There is a special abbreviated syntax used for this purpose:

<match no="1" postag="verb:.*perf">kierować</match>

This syntax means: take the POS tag of the first matched token that matches the regular expression specified in the postag attribute, and then apply this POS tag to the verb "kierować". This way the verb will be inflected just the way the matched verb was originally inflected. The reason why you need to specify the POS tag is that the matched token can have several POS tags (several readings).

Note that by default <match> element inside the <token> element inserts only a string – so it matches a string, and not part of speech tags. So even if it refers to a token with a POS tag, it copies the matched token, and not its POS token. However, you can use all above attributes to change the form of the token.

You can however use the <match> element to copy POS tags alone but to do so, you must use the attribute setpos="yes". All other attributes can be applied so that the POS could be converted appropriately. This can be useful for creating rules specifying grammatical agreement. Currently, such rules must be quite wordy, somewhat more terse syntax is in development.

Adding new Java rules

Rules that cannot be expressed with a simple pattern in grammar.xml can be developed as a Java class. As a developer, extend LanguageTool's Rule class and implement the match(AnalyzedSentence text) method.

See rules/WordRepeatRule.java for a simple example which you can use to develop your own rules. You will also need to add your rule's class to the getRelevantRules() method in <YourLanguage>.java to activate it.

Translating the user interface

We use Transifex to translate our property files. Updated translations are only copied to the LanguageTool source before a release, so if you need an early preview, say so on the LanguageTool mailing list and we'll update the files accordingly.

Background information

For some background information, Daniel Naber's diploma thesis about the original version of LanguageTool is available - please note that this refers to an earlier version of LanguageTool which was written in Python:

Page last modified: 2013-05-18