{"record":{"id":"ec009e70bde160b5","repo":"languagetool-org/languagetool","slug":"code-is-supposed-to-be-a-2-or-rarely-3-character","errorCode":null,"errorMessage":"code is supposed to be a 2 (or rarely 3) character code (unless it uses a format with variant, like xx-YY): ''","messagePattern":"code is supposed to be a 2 \\(or rarely 3\\) character code \\(unless it uses a format with variant, like xx-YY\\): ''","errorType":"console","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/HTTPServerConfig.java","lineNumber":532,"sourceCode":"          File dir = new File(ngramLangIdentData);\n          if (!dir.exists() || dir.isDirectory()) {\n            throw new IllegalArgumentException(\"ngramLangIdentData does not exist or is a directory (needs to be a ZIP file): \" + ngramLangIdentData);\n          }\n          setNgramLangIdentData(dir);\n        }\n      }\n    } catch (IOException e) {\n      throw new RuntimeException(\"Could not load properties from '\" + file + \"'\", e);\n    }\n  }\n\n  private void addDynamicLanguages(Properties props) throws IOException {\n    for (Object keyObj : props.keySet()) {\n      String key = (String)keyObj;\n      if (key.startsWith(\"lang-\") && !key.contains(\"-dictPath\")) {\n        String code = key.substring(\"lang-\".length());\n        if (!code.contains(\"-\") && code.length() != 2 && code.length() != 3) {\n          throw new IllegalArgumentException(\"code is supposed to be a 2 (or rarely 3) character code (unless it uses a format with variant, like xx-YY): '\" + code + \"'\");\n        }\n        String nameKey = \"lang-\" + code;\n        String name = props.getProperty(nameKey);\n        String dictPathKey = \"lang-\" + code + \"-dictPath\";\n        String dictPath = props.getProperty(dictPathKey);\n        if (dictPath == null) {\n          throw new IllegalArgumentException(dictPathKey + \" must be set\");\n        }\n        File dictPathFile = new File(dictPath);\n        if (!dictPathFile.exists() || !dictPathFile.isFile()) {\n          throw new IllegalArgumentException(\"dictionary file does not exist or is not a file: '\" + dictPath + \"'\");\n        }\n        ServerTools.print(\"Adding dynamic spell checker language \" + name + \", code: \" + code + \", dictionary: \" + dictPath);\n        Language lang = Languages.addLanguage(name, code, new File(dictPath));\n        // better fail early in case of misconfiguration, so use the language now:\n        if (!new File(lang.getCommonWordsPath()).exists()) {\n          throw new IllegalArgumentException(\"Common words path not found: '\" + lang.getCommonWordsPath() + \"'\");\n        }","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/HTTPServerConfig.java#L514-L550","documentation":"LanguageTool's server supports adding custom 'dynamic' languages at startup via properties like `lang-xx` in the server config. This error is thrown when the code extracted from a `lang-*` property key is not a 2- or 3-character language code (and contains no '-' for variant forms like xx-YY). It fails fast so a misconfigured custom language is caught before the server starts serving.","triggerScenarios":"A properties file passed via --config contains a key starting with `lang-` whose suffix (the part after `lang-`) is empty, 1 character, or longer than 3 characters and contains no hyphen, e.g. `lang-french=French` or `lang-=...`.","commonSituations":"Admins write the language NAME instead of the ISO code in the property key (`lang-german`), typos in the code (`lang-deutch`), or an empty key suffix after `lang-`.","solutions":["Rename the property key so the part after `lang-` is a 2- or 3-character ISO 639-1/639-3 code, e.g. `lang-de=German`","If you need a regional variant, use the hyphenated form like `lang-de-DE` (contains '-', so length check is skipped)","Remove the stray `lang-` prefixed key if it was not intended to define a custom language"],"exampleFix":"// before\nlang-french=French\n// after\nlang-fr=French","handlingStrategy":"validation","validationCode":"for (String key : props.stringPropertyNames()) {\n  if (key.startsWith(\"lang-\")) {\n    String code = key.substring(\"lang-\".length());\n    if (!code.contains(\"-\") && code.length() != 2 && code.length() != 3)\n      throw new IllegalStateException(\"Invalid dynamic language code in key: \" + key);\n  }\n}","typeGuard":"boolean isValidLangCode(String code) {\n  return code != null && (code.contains(\"-\") || code.length() == 2 || code.length() == 3);\n}","tryCatchPattern":null,"preventionTips":["Always use ISO 639-1 (2-letter) or 639-3 (3-letter) codes in lang-* property keys","Use the xx-YY hyphenated form for regional variants","Lint server config files at deploy time for lang- prefixed keys"],"tags":["configuration","server","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}