« MediaWiki:Gadget-CreerNouveauMot.js » : différence entre les versions

Définition, traduction, prononciation, anagramme et synonyme sur le dictionnaire libre Wiktionnaire.
Contenu supprimé Contenu ajouté
Aucun résumé des modifications
reordering sections
Ligne 19 : Ligne 19 :
* v5.0.2 2020-08-02 added missing sections
* v5.0.2 2020-08-02 added missing sections
* v5.0.3 2020-08-05 added date template in etymology section
* v5.0.3 2020-08-05 added date template in etymology section
* v5.0.4 2020-08-05 reordering level 4 sections
* ------------------------------------------------------------------------------------
* ------------------------------------------------------------------------------------
* [[Catégorie:JavaScript du Wiktionnaire|CreerNouveauMot.js]]
* [[Catégorie:JavaScript du Wiktionnaire|CreerNouveauMot.js]]
Ligne 27 : Ligne 28 :
NAME: "Créer nouveau mot",
NAME: "Créer nouveau mot",


VERSION: "5.0.3",
VERSION: "5.0.4",


_COOKIE_NAME: "cnm_last_lang",
_COOKIE_NAME: "cnm_last_lang",
Ligne 54 : Ligne 55 :
*/
*/
_SECTIONS: [
_SECTIONS: [
{label: "Variantes orthographiques", code: "variantes orthographiques", level: 4},
{label: "Autre alphabet ou système d’écriture", code: "écriture", level: 4},
{label: "Autre alphabet ou système d’écriture", code: "écriture", level: 4},
{label: "Variantes orthographiques", code: "variantes orthographiques", level: 4},
{label: "Variantes", code: "variantes", level: 4},
{label: "Variantes", code: "variantes", level: 4},
{label: "Transcriptions", code: "transcriptions", level: 4},
{label: "Transcriptions", code: "transcriptions", level: 4},

Version du 5 août 2020 à 12:11

/**
 * (fr)
 * Ce gadget permet de facilement créer une entrée dans une langue donnée en
 * remplissant quelques champs de texte.
 * ------------------------------------------------------------------------------------
 * (en)
 * This gadget helps create new entries for a given language by filling out some
 * text fields.
 * ------------------------------------------------------------------------------------
 * v2.0 2012-12-10
 * v2.1 2012-12-26
 * v2.2 2013-01-01
 * v2.3 2013-01-04 dialog box functions restructuration
 * v2.4 2013-01-29 cookies to store preferences
 * v3.0 2013-02-28 tool integration into pages
 * v4.0 2014-01-22 support for new editable sections syntax
 * v5.0 2020-07-29 full rewrite, migration to OOUI
 * v5.0.1 2020-08-01 using {{lien}} for links, reworked toolbar
 * v5.0.2 2020-08-02 added missing sections
 * v5.0.3 2020-08-05 added date template in etymology section
 * v5.0.4 2020-08-05 reordering level 4 sections
 * ------------------------------------------------------------------------------------
 * [[Catégorie:JavaScript du Wiktionnaire|CreerNouveauMot.js]]
 */

mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-toolbars", "oojs-ui-windows"], function () {
  window.wikt.gadgets.creerNouveauMot = {
    NAME: "Créer nouveau mot",

    VERSION: "5.0.4",

    _COOKIE_NAME: "cnm_last_lang",
    /** Cookie duration in days. */
    _COOKIE_DURATION: 30,

    /**
     * List of sister projects and associated templates and domain names.
     * @type {Object<string, Object<string, string>>}
     */
    _OTHER_PROJECTS: {
      w: {label: "Wikipédia", templateName: "WP", urlDomain: "fr.wikipedia.org"},
      s: {label: "Wikisource", templateName: "WS", urlDomain: "fr.wikisource.org"},
      q: {label: "Wikiquote", templateName: "WQ", urlDomain: "fr.wikiquote.org"},
      v: {label: "Wikiversité", templateName: "WV", urlDomain: "fr.wikiversity.org"},
      l: {label: "Wikilivres", templateName: "WL", urlDomain: "fr.wikibooks.org"},
      species: {label: "Wikispecies", templateName: "WSP", urlDomain: "wikispecies.org"},
      voy: {label: "Wikivoyage", templateName: "VOY", urlDomain: "fr.wikivoyage.org"},
      n: {label: "Wikinews", templateName: "WN", urlDomain: "fr.wikinews.org"},
      c: {label: "Commons", templateName: "Commons", urlDomain: "commons.wikimedia.org"},
    },

    /**
     * List of word type subsections.
     * @type {Array<Object<string, string|boolean>>}
     */
    _SECTIONS: [
      {label: "Autre alphabet ou système d’écriture", code: "écriture", level: 4},
      {label: "Variantes orthographiques", code: "variantes orthographiques", level: 4},
      {label: "Variantes", code: "variantes", level: 4},
      {label: "Transcriptions", code: "transcriptions", level: 4},
      {label: "Abréviations", code: "abréviations", level: 4},
      {label: "Augmentatifs", code: "augmentatifs", level: 4},
      {label: "Diminutifs", code: "diminutifs", level: 4},
      {label: "Synonymes", code: "synonymes", level: 4},
      {label: "Quasi-synonymes", code: "quasi-synonymes", level: 4},
      {label: "Gentilés", code: "gentilés", level: 4},
      {label: "Antonymes", code: "antonymes", level: 4},
      {label: "Composés", code: "composés", level: 4},
      {label: "Dérivés", code: "dérivés", level: 4},
      {label: "Apparentés étymologiques", code: "apparentés étymologiques", level: 4},
      {label: "Vocabulaire", code: "vocabulaire", level: 4},
      {label: "Phrases et expressions", code: "phrases", level: 4},
      {label: "Variantes dialectales", code: "variantes dialectales", level: 4},
      {label: "Hyperonymes", code: "hyperonymes", level: 4},
      {label: "Hyponymes", code: "hyponymes", level: 4},
      {label: "Holonymes", code: "holonymes", level: 4},
      {label: "Méronymes", code: "méronymes", level: 4},
      {label: "Hyper-verbes", code: "hyper-verbes", level: 4},
      {label: "Troponymes", code: "troponymes", level: 4},
      {label: "Traductions", code: "traductions", level: 4, hidden: true},
      {label: "Dérivés dans d’autres langues", code: "dérivés autres langues", level: 4},
      {label: "Faux-amis", code: "faux-amis", level: 4},
      {label: "Homophones", code: "homophones", section: "prononciation", level: 4, needsLang: true},
      {label: "Paronymes", code: "paronymes", section: "prononciation", level: 4},
      {label: "Anagrammes", code: "anagrammes", level: 3},
    ],

    /**
     * Main word.
     * @type {string}
     * @private
     */
    _word: mw.config.get("wgTitle").replace("_", " "),

    /**
     * Currently selected language.
     * @type {wikt.gadgets.creerNouveauMot.Language}
     * @private
     */
    _selectedLanguage: null,

    /**
     * List of available languages.
     * @type {Array<wikt.gadgets.creerNouveauMot.Language>}
     * @private
     */
    _languages: [],

    /**
     * Start up GUI.
     * @type {wikt.gadgets.creerNouveauMot.StartGui}
     * @private
     */
    _startGui: null,

    /**
     * Main GUI.
     * @type {wikt.gadgets.creerNouveauMot.MainGui}
     * @private
     */
    _gui: null,

    /*
     * Public functions
     */

    /**
     * Initializes this gadget.
     */
    init: function () {
      // Sorting languages: french first,
      // then all remaining in lexicographical order.
      this._languages.sort(function (a, b) {
        if (a.code === "fr") {
          return -1;
        }
        else if (b.code === "fr") {
          return 1;
        }
        else {
          return a.name.localeCompare(b.name);
        }
      });

      if ($(this.Gui.prototype.TARGET_ELEMENT)) {
        this._generateStartUi();
      }
    },

    /**
     * Adds a language to the list of available languages.
     * @param language {wikt.gadgets.creerNouveauMot.Language} The language to add.
     */
    addLanguage: function (language) {
      this._languages.push(language);
    },

    /**
     * Fecthes the language with the given code.
     * @param languageCode {string} Language code.
     * @returns {wikt.gadgets.creerNouveauMot.Language|null} The language object or null if none were found.
     */
    getLanguage: function (languageCode) {
      for (var i = 0; i < this._languages.length; i++) {
        var lang = this._languages[i];
        if (lang.code === languageCode) {
          return lang;
        }
      }
      return null;
    },

    /*
     * Private functions
     */

    /**
     * Generates the start up GUI.
     * @private
     */
    _generateStartUi: function () {
      this._startGui = new this.StartGui(this.NAME, this._generateMainUi.bind(this));
    },

    /**
     * Generates the main GUI.
     * @private
     */
    _generateMainUi: function () {
      this._gui = new this.MainGui(
          this._word,
          this._languages,
          this._SECTIONS,
          this._onLanguageSelect.bind(this),
          this._onClassSelect.bind(this),
          this._insertWikicode.bind(this),
          this._OTHER_PROJECTS
      );

      var previousLang = wikt.cookie.read(this._COOKIE_NAME);
      this._onLanguageSelect(previousLang || this._languages[0].code);
      this._gui.sortingKey = wikt.page.getSortingKey(this._word);
      this._gui.isDraft = false;
    },

    /**
     * Generates the wikicode then inserts it into the edit box.
     * @private
     */
    _insertWikicode: function () {
      var word = this._word;
      var langCode = this._selectedLanguage.code;
      var isDraft = this._gui.isDraft;
      var etymology = this._gui.etymology || ": {{date|lang={0}}} {{ébauche-étym|{0}}}".format(langCode);
      var pron = this._gui.pronunciation;
      var isConv = langCode === "conv";

      var grammarItem = this._selectedLanguage.getGrammarItem(this._gui.grammarClass);
      var gender = grammarItem.getGender(this._gui.gender);
      var number = grammarItem.getNumber(this._gui.number);
      var grammarClass = grammarItem.grammaticalClass;
      var inflectionsTemplate = grammarItem.getInflectionsTemplate(word, gender.label, number.label, pron);

      var definition = this._gui.definition || "# {{ébauche-déf|{0}}}".format(langCode);

      if (!definition.includes("#*")) {
        definition += "\n#* {{ébauche-exe|{0}}}".format(langCode);
      }

      var references = this._gui.references;
      var sources = this._gui.sources;
      var bibliography = this._gui.bibliography;
      var sortingKey = this._gui.sortingKey;

      var wikicode = "== {{langue|{0}}} ==\n".format(langCode)
          + (isDraft ? "{{ébauche|{0}}}\n".format(langCode) : "")
          + "=== {{S|étymologie}} ===\n"
          + etymology + "\n\n"
          + "=== {{S|{0}|{1}}} ===\n".format(grammarClass.sectionCode, langCode)
          + (inflectionsTemplate ? inflectionsTemplate + "\n" : "");
      wikicode += "'''{0}'''".format(word);
      if (isConv) {
        wikicode += "\n";
      }
      else {
        // trim() to remove trailing space(s) if no gender or number template.
        wikicode += " " + "{{pron|{0}|{1}}} {2} {3}".format(pron, langCode, gender.template.format(langCode), number.template)
            .replace(/\s+/g, " ").trim() + "\n";
      }
      wikicode += definition + "\n\n";

      function linkify(content) {
        var lines = content.split("\n");

        for (var i = 0; i < lines.length; i++) {
          var line = lines[i];

          if (/^[^=#:;\[{\s][^\s]*/.test(line)) {
            lines[i] = "* {{lien|{0}|{1}}}".format(line.trim(), langCode);
          }
          else if (/^\*\s*[^\s]+/.test(line)) {
            lines[i] = "* {{lien|{0}|{1}}}".format(line.substring(1).trim(), langCode);
          }
        }

        return lines.join("\n");
      }

      for (var i = 0; i < this._SECTIONS.length; i++) {
        var section = this._SECTIONS[i];
        var sectionCode = section.code;

        if (sectionCode !== "traductions" || langCode === "fr") {
          var content = sectionCode !== "traductions"
              ? this._gui.getSectionContent(section.code)
              : "{{trad-début}}\n"
              + "{{ébauche-trad}}\n"
              + "{{trad-fin}}\n\n";
          var upperSection = section.section;
          var titleLevel;

          if (content) {
            if (upperSection && !wikicode.includes("S|" + upperSection)) {
              titleLevel = Array(section.level).join("=");
              wikicode += "{1} {{S|{0}}} {1}\n".format(upperSection, titleLevel);
            }
            titleLevel = Array(section.level + 1).join("=");
            wikicode += "{2} {{S|{0}{1}}} {2}\n".format(section.code, section.needsLang ? ("|" + langCode) : "", titleLevel)
                + linkify(content).trim() + "\n\n";
          }
        }
      }

      var seeAlso = "";
      for (var projectCode in this._OTHER_PROJECTS) {
        if (this._OTHER_PROJECTS.hasOwnProperty(projectCode)) {
          var addLink = this._gui.hasAddLinkToProject(projectCode);

          if (addLink) {
            var projectModelParams = this._gui.getProjectLinkParams(projectCode);
            var templateName = this._OTHER_PROJECTS[projectCode].templateName;

            if (seeAlso === "") {
              seeAlso = "=== {{S|voir aussi}} ===\n";
            }
            seeAlso += "* {{{0}{1}|lang={2}}}\n".format(templateName, projectModelParams ? "|" + projectModelParams : "", langCode);
          }
        }
      }
      if (seeAlso) {
        seeAlso += "\n";
      }
      wikicode += seeAlso;

      var containsRefTemplates = /{{(R|RÉF|réf)\||<ref>.+<\/ref>/gm.test(wikicode);

      if (containsRefTemplates || references || bibliography) {
        var insertSourcesSection = containsRefTemplates || sources;

        if (references || insertSourcesSection || bibliography) {
          wikicode += "=== {{S|références}} ===\n";
          if (references) {
            wikicode += references + "\n\n";
          }
        }
        if (insertSourcesSection) {
          wikicode += "==== {{S|sources}} ====\n{{Références}}" + (sources ? "\n" : "") + sources + "\n\n";
        }
        if (bibliography) {
          wikicode += "==== {{S|bibliographie}} ====\n" + bibliography + "\n\n";
        }
      }

      wikicode += (sortingKey !== word ? "{{clé de tri|{0}}}\n".format(sortingKey) : "");

      wikt.edit.insertText(wikt.edit.getCursorLocation(), wikicode);

      var $summaryFld = wikt.edit.getEditSummaryField();
      var summary = $summaryFld.val();
      var comment = this._editComment();

      if (!summary.includes(comment)) {
        $summaryFld.val(comment + " " + summary);
      }
    },

    /**
     * Function called whenever the user selects a language.
     * @param languageCode {string} Code of the selected language.
     * @private
     */
    _onLanguageSelect: function (languageCode) {
      languageCode = languageCode.trim();

      if (languageCode !== "") {
        var language = this.getLanguage(languageCode);

        if (!language) {
          language = new this.Language(languageCode, languageCode, [], [
            new wikt.gadgets.creerNouveauMot.GrammaticalItem(
                new wikt.gadgets.creerNouveauMot.GrammaticalClass("<em>indisponible</em>", "<!-- À compléter -->")
            )
          ]);
        }
        this._selectedLanguage = language;
        wikt.cookie.create(this._COOKIE_NAME, language.code, this._COOKIE_DURATION);
        this._gui.selectLanguage(this._selectedLanguage);

        if (!this._gui.pronunciation) {
          this._gui.pronunciation = language.generatePronunciation(this._word);
        }
      }
    },

    /**
     * Function called whenever the user selects a grammatical class.
     * @param className {string} Code of the selected grammatical class.
     * @private
     */
    _onClassSelect: function (className) {
      var grammarItem = this._selectedLanguage.getGrammarItem(className);
      this._gui.setAvailableGenders(grammarItem.availableGenders);
      this._gui.setAvailableNumbers(grammarItem.availableNumbers);
    },

    /**
     * Returns the edit comment.
     * It is a function and not an attribute as “this.VERSION” would not be defined yet.
     * @return {string}
     * @private
     */
    _editComment: function () {
      return "Ajout d’un mot assisté par [[Aide:Gadget-CreerNouveauMot|Gadget-CreerNouveauMot]] (v{0})".format(this.VERSION);
    },
  };

  /**
   * This class encapsulates data and behaviors specific to the given language.
   * @param code {string} Language code defined in [[Module:langues/data]].
   * @param name {string} Language’s name.
   * @param ipaSymbols {Array<Array<string>>?} An optional list of common IPA symbols for the language.
   * @param grammarItems {Array<wikt.gadgets.creerNouveauMot.GrammaticalItem>?} An optional list of grammatical items.
   * @param pronGenerator {Function?} An optional function that generates an approximate pronunciation
   * based on the word.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.Language = function (code, name, ipaSymbols, grammarItems, pronGenerator) {
    /** @type {string} */
    this._code = code;
    /** @type {string} */
    this._name = name;
    /** @type {Array<Array<string>>} */
    this._ipaSymbols = ipaSymbols || [];
    /** @type {Object<string, wikt.gadgets.creerNouveauMot.GrammaticalItem>} */
    this._grammarItems = {};
    /** @type {Function} */
    this._pronGenerator = pronGenerator || function () {
      return "";
    };

    grammarItems = grammarItems || [];
    for (var i = 0; i < grammarItems.length; i++) {
      var grammarItem = grammarItems[i];
      this._grammarItems[grammarItem.grammaticalClass.sectionCode] = grammarItem;
    }
  };

  wikt.gadgets.creerNouveauMot.Language.prototype = {
    /**
     * @return {string} This language code.
     */
    get code() {
      return this._code;
    },

    /**
     * @return {string} This language’s name.
     */
    get name() {
      return this._name;
    },

    /**
     * @return {Array<Array<string>>} The IPA symbols for this language.
     */
    get ipaSymbols() {
      return this._ipaSymbols;
    },

    /**
     * @return {Object<string, wikt.gadgets.creerNouveauMot.GrammaticalItem>} The grammatical items for this language.
     */
    get grammarItems() {
      return this._grammarItems;
    },

    /**
     * Fetches the grammatical item that has the given section title.
     * @param sectionName {string} Section’s title.
     * @return {wikt.gadgets.creerNouveauMot.GrammaticalItem} The grammatical item if found or undefined otherwise.
     */
    getGrammarItem: function (sectionName) {
      return this._grammarItems[sectionName];
    },

    /**
     * Generates the pronunciation of the given word for this language.
     * @param word {string} The word.
     * @return {string} The pronunciation or an empty string if no function was defined in the constructor.
     */
    generatePronunciation: function (word) {
      return this._pronGenerator(word);
    }
  };

  /**
   * Wrapper class for OO.ui.TabPanelLayout.
   * @param name {string} Tab’s name.
   * @param options {Object} OOUI tab’s options.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.Tab = function (name, options) {
    OO.ui.TabPanelLayout.call(this, name, options);
  };

  // Inherit from OOUI TabPanelLayout’s prototype.
  wikt.gadgets.creerNouveauMot.Tab.prototype = Object.create(OO.ui.TabPanelLayout.prototype);

  /**
   * Sets this tab as active.
   */
  wikt.gadgets.creerNouveauMot.Tab.prototype.select = function () {
    this.setActive(true);
  };

  /**
   * Base class for GUIs.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.Gui = function () {
  };

  wikt.gadgets.creerNouveauMot.Gui.prototype = {
    /** jQuery selector of the HTML element GUIs will be inserted into. */
    TARGET_ELEMENT: "#Editnotice-0",
  }

  /**
   * Inherits from wikt.gadgets.creerNouveauMot.Gui.
   * @param gadgetName {string}
   * @param onActivateGadget {function}
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.StartGui = function (gadgetName, onActivateGadget) {
    wikt.gadgets.creerNouveauMot.Gui.call(this);

    var $target = $(this.TARGET_ELEMENT);

    $target.html(('<div class="center" style="margin-bottom: 5px"><span id="cnm-open-ui" class="mw-ui-button mw-ui-progressive">Ouvrir le gadget {0}</span></div>'
        + '<strong><em>{0}</em></strong> est un outil qui vous aide à ajouter des mots sur le Wiktionnaire '
        + 'sans avoir besoin de tout comprendre à la syntaxe wiki. '
        + 'Voir <a href="/wiki/Aide:Gadget-CreerNouveauMot" target="_blank">l’aide</a> pour plus d’explications.').format(gadgetName));
    $target.find("#cnm-open-ui").on("click", onActivateGadget);
  };

  // Inherit from gadget Gui’s prototype.
  wikt.gadgets.creerNouveauMot.StartGui.prototype = Object.create(wikt.gadgets.creerNouveauMot.Gui.prototype);

  /**
   * Inherits from wikt.gadgets.creerNouveauMot.Gui.
   * @param word {string} The word.
   * @param languages {Array<wikt.gadgets.creerNouveauMot.Language>} The language.
   * @param sections {Array<Object<string, string>>} The list of word type sub-sections.
   * @param onLanguageSelect {Function<string|null, void>} Callback function for when a language is selected.
   * @param onClassSelect {Function} Callback function for when a grammatical class is selected.
   * @param onInsertWikicode {Function} Callback function for when “insert wikicode” button is clicked.
   * @param otherProjects {Object<string, string>} Object containing data for sister projects.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.MainGui = function (word, languages, sections, onLanguageSelect, onClassSelect, onInsertWikicode, otherProjects) {
    wikt.gadgets.creerNouveauMot.Gui.call(this);

    /**
     * Tabs list.
     * @type {Array<wikt.gadgets.creerNouveauMot.Tab>}
     */
    this._tabs = [];
    this._languageFld = null;
    this._languageSelectFld = null;
    this._languageBnt = null;
    this._pronunciationFld = null;
    this._pronunciationPnl = null;
    this._grammarClassSelectFld = null;
    this._gendersFld = null;
    this._numbersFld = null;
    this._definitionFld = null;
    this._etymologyFld = null;
    this._referencesFld = null;
    this._sourcesFld = null;
    this._bibliographyFld = null;
    /**
     * Word type sub-sections list.
     * @type {Object<string, Object>}
     */
    this._otherSectionFields = {};
    this._draftChk = null;
    this._seeOtherProjectsChk = {};
    this._sortKeyFld = null;

    // Deleting all content above edit box.
    $("#nouvel-article").parent().remove();

    var $tedit = $(this.TARGET_ELEMENT);

    var specialChars = "’àÀâÂæÆçÇéÉèÈêÊëËîÎïÏôÔœŒùÙûÛüÜÿŸ".split("");
    specialChars.push("«\u00a0");
    specialChars.push("\u00a0»");

    // Alias to avoid confusion inside nested functions.
    var self = this;

    // Defining all tabs.
    var tabs = [
      {
        title: "Langue, type, définition",
        content: function () {
          self._languageFld = new OO.ui.TextInputWidget();
          self._languageBnt = new OO.ui.ButtonWidget({
            label: "Passer à cette langue",
          });
          self._languageBnt.on("click", function () {
            // noinspection JSCheckFunctionSignatures,JSValidateTypes
            onLanguageSelect(self._languageFld.getValue());
          });

          var languageOptions = [];
          for (var i = 0; i < languages.length; i++) {
            var lang = languages[i];

            languageOptions.push(new OO.ui.MenuOptionWidget({
              data: lang.code,
              label: lang.name,
            }));
          }
          self._languageSelectFld = new OO.ui.DropdownWidget({
            label: "Choisissez",
            menu: {
              items: languageOptions,
            },
          });
          self._languageSelectFld.getMenu().on("select", function (e) {
            // noinspection JSCheckFunctionSignatures,JSValidateTypes
            onLanguageSelect(e.getData());
          });

          self._grammarClassSelectFld = new OO.ui.DropdownWidget({
            label: "Choisissez",
          });
          self._grammarClassSelectFld.getMenu().on("select", function (e) {
            // noinspection JSCheckFunctionSignatures
            onClassSelect(e.getData());
          });
          self._gendersFld = new OO.ui.DropdownWidget({
            label: "Choisissez",
          });
          self._numbersFld = new OO.ui.DropdownWidget({
            label: "Choisissez",
          });

          self._pronunciationFld = new OO.ui.TextInputWidget({
            id: "cnm-pronunciation-field",
          });
          self._pronunciationPnl = new OO.ui.FieldLayout(self._pronunciationFld, {
            align: "inline",
          });

          self._definitionFld = new OO.ui.MultilineTextInputWidget({
            rows: 4,
          });

          return new OO.ui.FieldsetLayout({
            items: [
              new OO.ui.FieldsetLayout({
                label: "Langue",
                items: [
                  new OO.ui.HorizontalLayout({
                    items: [
                      new OO.ui.ActionFieldLayout(self._languageFld, self._languageBnt),
                      new OO.ui.FieldLayout(self._languageSelectFld, {
                        expanded: false,
                      }),
                    ],
                  }),
                ],
              }),
              new OO.ui.FieldsetLayout({
                label: "Informations grammaticales",
                items: [
                  new OO.ui.HorizontalLayout({
                    items: [
                      new OO.ui.FieldLayout(self._grammarClassSelectFld, {
                        expanded: false,
                      }),
                      new OO.ui.FieldLayout(self._gendersFld, {
                        expanded: false,
                      }),
                      new OO.ui.FieldLayout(self._numbersFld, {
                        expanded: false,
                      }),
                    ],
                  }),
                ],
              }),
              new OO.ui.FieldsetLayout({
                label: "Prononciation",
                items: [
                  self._pronunciationPnl,
                ],
              }),
              new OO.ui.FieldsetLayout({
                label: "Définition",
                items: [
                  new OO.ui.FieldLayout(self._definitionFld, {
                    label: self._createLinks(specialChars, self._definitionFld),
                    align: "inline",
                  }),
                ],
              }),
            ],
          });
        },
      },
      {
        title: "Sections supplémentaires",
        content: function () {
          self._etymologyFld = new OO.ui.MultilineTextInputWidget({
            rows: 4,
            autofocus: true,
          });
          self._referencesFld = new OO.ui.MultilineTextInputWidget({
            rows: 4,
          });
          self._sourcesFld = new OO.ui.MultilineTextInputWidget({
            rows: 4,
          });
          self._bibliographyFld = new OO.ui.MultilineTextInputWidget({
            rows: 4,
          });

          var fields = [];
          for (var i = 0; i < sections.length; i++) {
            var section = sections[i];

            if (!section.hidden) {
              var field = new OO.ui.MultilineTextInputWidget({
                rows: 4,
                columns: 20,
              });
              self._otherSectionFields[section.code] = field;
              fields.push(new OO.ui.FieldLayout(field, {
                label: section.label,
                align: "inline",
              }));
            }
          }

          return new OO.ui.FieldsetLayout({
            items: [
              new OO.ui.FieldsetLayout({
                label: "Étymologie",
                items: [
                  new OO.ui.FieldLayout(self._etymologyFld, {
                    label: self._createLinks(specialChars, self._etymologyFld),
                    align: "inline",
                  }),
                ],
                help: new OO.ui.HtmlSnippet(wikt.page.renderWikicode(
                    "Laisser le champ de texte vide ajoutera le modèle «&nbsp;[[Modèle:ébauche-étym|ébauche-étym]]&nbsp;».",
                    true
                )),
                helpInline: true,
              }),
              new OO.ui.FieldsetLayout({
                label: "Références",
                items: [
                  new OO.ui.FieldLayout(self._referencesFld, {
                    label: self._createLinks(specialChars, self._referencesFld),
                    align: "inline",
                  }),
                  new OO.ui.FieldLayout(self._sourcesFld, {
                    label: self._createLinks(specialChars, self._sourcesFld, "", "Sources"),
                    align: "inline",
                  }),
                  new OO.ui.FieldLayout(self._bibliographyFld, {
                    label: self._createLinks(specialChars, self._bibliographyFld, "", "Bibliographie"),
                    align: "inline",
                  }),
                ],
                help: new OO.ui.HtmlSnippet(wikt.page.renderWikicode(
                    "Le modèle <code>{{[[Modèle:Références|Références]]}}</code> est ajouté automatiquement au début de la section «&nbsp;Sources&nbsp;».",
                    true
                )),
                helpInline: true,
              }),
              new OO.ui.FieldsetLayout({
                label: "Autres sections",
                items: [
                  new OO.ui.HorizontalLayout({
                    items: fields,
                  }),
                ],
              }),
            ],
          });
        },
      },
      {
        title: "Options avancées",
        content: function () {
          var otherProjectsFields = [];
          var linksEnabled = false;

          for (var projectCode in otherProjects) {
            if (otherProjects.hasOwnProperty(projectCode)) {
              // * DO NOT REMOVE FUNCTION *
              // This function in necessary to avoid “textFld”
              // changing value in checkbox.on() after each iteration.
              (function () {
                var projectName = otherProjects[projectCode].label;
                var projectDomain = otherProjects[projectCode].urlDomain;
                var checkbox = new OO.ui.CheckboxInputWidget({
                  value: projectCode,
                  selected: linksEnabled,
                });
                var textFld = new OO.ui.TextInputWidget({
                  label: projectName,
                  disabled: !linksEnabled,
                });

                checkbox.on("change", function (selected) {
                  textFld.setDisabled(!selected);
                });

                self._seeOtherProjectsChk[projectCode] = {
                  "checkbox": checkbox,
                  "textfield": textFld,
                };

                var url = "https://{0}/wiki/{1}".format(projectDomain, encodeURI(word));
                // noinspection HtmlUnknownTarget
                var label = new OO.ui.HtmlSnippet('<a href="{0}" target="_blank">Rechercher</a>'.format(url));

                otherProjectsFields.push(new OO.ui.ActionFieldLayout(
                    checkbox,
                    textFld,
                    {
                      align: "inline",
                      id: "sister-project-{0}".format(projectCode),
                      label: label,
                    }
                ));
              })();
            }
          }

          self._draftChk = new OO.ui.CheckboxInputWidget();
          self._sortKeyFld = new OO.ui.TextInputWidget();

          return new OO.ui.FieldsetLayout({
            items: [
              new OO.ui.FieldsetLayout({
                label: "Liens vers les autres projets",
                items: otherProjectsFields,
                help: "Les champs de texte permettent de renseigner des paramètres" +
                    " supplémentaire aux modèles de liens interwiki.",
                helpInline: true,
              }),
              new OO.ui.FieldsetLayout({
                label: "Autres options",
                items: [
                  new OO.ui.FieldLayout(self._draftChk, {
                    label: "Ébauche",
                    align: "inline",
                  }),
                  new OO.ui.FieldLayout(self._sortKeyFld, {
                    label: "Clé de tri",
                    help: "Permet de trier les pages dans les catégories.",
                    align: "inline",
                    helpInline: true,
                  }),
                ],
              }),
            ],
          });
        },
      },
    ];

    /*
     * Inserting tabs
     */

    var tabsWidget = new OO.ui.IndexLayout({
      expanded: false,
      id: "cnm-tabs-widget",
    });
    for (var i = 0; i < tabs.length; i++) {
      var tab = new wikt.gadgets.creerNouveauMot.Tab('cnm-tab{0}'.format(i), {
        label: tabs[i].title,
        expanded: false,
      });
      var content = tabs[i].content();
      tab.$element.append(typeof content === "string" ? content : content.$element);
      tabsWidget.addTabPanels([tab]);
      this._tabs.push(tab);
    }

    /*
     * Constructing GUI
     */

    // TODO afficher le texte quelque part
    // var popup = new OO.ui.PopupWidget({
    //   // $autoCloseIgnore: button.$element,
    //   $content: $("<p>Le code a été inséré dans la boite d’édition ci-dessous. " +
    //       "Vous devriez <strong>vérifier</strong> que le résultat est conforme à vos souhaits, " +
    //       "et en particulier utiliser le bouton «&nbsp;Prévisualer&nbsp;» avant de publier.</p>"),
    //   padded: true,
    //   width: 300,
    //   anchor: false,
    // });

    var toolFactory = new OO.ui.ToolFactory();
    var toolGroupFactory = new OO.ui.ToolGroupFactory();
    var toolbar = new OO.ui.Toolbar(toolFactory, toolGroupFactory, {actions: true});

    /**
     * Adds a custom button to the tool factory.
     * @param toolFactory The tool factory into which the tool will be registered.
     * @param name {string} Button’s name.
     * @param icon {string|null} Buttons’s icon name.
     * @param progressive {boolean} Wether the icon should be marked as progressive.
     * @param title {string} Button’s tooltip text.
     * @param onSelect {function} Callback for when the button is clicked.
     * @param onUpdateState {function?} Callback for when the button changes state (optional).
     * @param displayBothIconAndLabel {boolean} Whether both the icon and label should be displayed.
     */
    function generateButton(toolFactory, name, icon, progressive, title, onSelect, onUpdateState, displayBothIconAndLabel) {
      /** @constructor */
      function CustomTool() {
        CustomTool.super.apply(this, arguments);
      }

      OO.inheritClass(CustomTool, OO.ui.Tool);
      CustomTool.static.name = name;
      CustomTool.static.icon = icon;
      CustomTool.static.title = title;
      if (progressive) {
        CustomTool.static.flags = ["primary", "progressive"];
      }
      CustomTool.static.displayBothIconAndLabel = !!displayBothIconAndLabel;
      CustomTool.prototype.onSelect = onSelect;
      CustomTool.prototype.onUpdateState = onUpdateState || function () {
        this.setActive(false);
      };

      toolFactory.register(CustomTool);
    }

    var hideBtn = "hide";
    generateButton(toolFactory, hideBtn, "eyeClosed", false, "Masquer", function () {
      // noinspection JSCheckFunctionSignatures
      tabsWidget.toggle();
      this.setTitle(tabsWidget.isVisible() ? "Masquer" : "Afficher");
      this.setIcon(tabsWidget.isVisible() ? "eyeClosed" : "eye");
    });

    var helpBtn = "help";
    generateButton(toolFactory, helpBtn, "help", false, "Aide (s’ouvre dans un nouvel onglet)", function () {
      window.open("/wiki/Aide:Gadget-CreerNouveauMot");
    });

    var actionsToolbar = new OO.ui.Toolbar(toolFactory, toolGroupFactory);

    var insertWikicodeBtn = "insert";
    generateButton(toolFactory, insertWikicodeBtn, null, true, "Insérer le code", onInsertWikicode, null, true);

    actionsToolbar.setup([
      {
        type: "bar",
        include: [insertWikicodeBtn],
      },
    ]);

    toolbar.setup([
      {
        type: "bar",
        include: [hideBtn, helpBtn],
      },
    ]);
    toolbar.$actions.append(actionsToolbar.$element);

    var gadgetBox = new OO.ui.PanelLayout({
      expanded: false,
      framed: true,
    });
    var contentFrame = new OO.ui.PanelLayout({
      expanded: false,
    });

    gadgetBox.$element.append(
        toolbar.$element,
        contentFrame.$element.append(tabsWidget.$element)
    );

    toolbar.initialize();
    toolbar.emit("updateState");

    $tedit.html(gadgetBox.$element);
    // Allows layout taking all available width.
    // gadgetBox.$element.removeClass("oo-ui-fieldLayout-align-left");

    for (var projectCode in otherProjects) {
      if (otherProjects.hasOwnProperty(projectCode)) {
        $("#sister-project-{0} span.oo-ui-actionFieldLayout-button".format(projectCode)).attr("style", "width: 100%");
        $("#sister-project-{0} span.oo-ui-fieldLayout-field".format(projectCode)).attr("style", "width: 100%");
      }
    }

    // Enforce fonts for pronunciation text input.
    $("#cnm-pronunciation-field > input").attr("style",
        'font-family:' +
        '"Segoe UI","Calibri","DejaVu Sans","Charis SIL","Doulos SIL",' +
        '"Gentium Plus","Gentium","GentiumAlt","Lucida Grande",' +
        '"Arial Unicode MS",sans-serif !important');
    // Remove class as to remove the gap between the tabs panel and the frame.
    // $("#cnm-tabs-widget").parent().removeClass("oo-ui-panelLayout-padded");
  };

  wikt.gadgets.creerNouveauMot.MainGui.prototype = Object.create(wikt.gadgets.creerNouveauMot.Gui.prototype);

  /*
   * Public methods
   */

  /**
   * Selects the tab at the given index.
   * @param index {number} The index.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.selectTab = function (index) {
    this._tabs[index].select();
  };

  /**
   * Selects the given language.
   * If the language is not in the dropdown menu, it is added to it.
   * @param language {wikt.gadgets.creerNouveauMot.Language} The language object.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.selectLanguage = function (language) {
    if (!this._languageSelectFld.getMenu().findItemFromData(language.code)) {
      this._languageSelectFld.getMenu().addItems([new OO.ui.MenuOptionWidget({
        data: language.code,
        label: language.name,
      })], 0);
    }
    this._updateFields(language);
    this._languageSelectFld.getMenu().selectItemByData(language.code);
    this._pronunciationPnl.setLabel(this._formatApi(language.ipaSymbols));
  };

  /**
   * Sets the available genders widget.
   * @param genders {Array<wikt.gadgets.creerNouveauMot.Gender>} The list of genders.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.setAvailableGenders = function (genders) {
    this._setListValues(genders, this._gendersFld);
  };

  /**
   * Sets the available grammatical numbers widget.
   * @param numbers {Array<wikt.gadgets.creerNouveauMot.Number>} The list of grammatical numbers.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.setAvailableNumbers = function (numbers) {
    this._setListValues(numbers, this._numbersFld);
  };

  /*
   * Private methods
   */

  /**
   * Updates all language-related fields.
   * @param language {wikt.gadgets.creerNouveauMot.Language} The selected language.
   * @private
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype._updateFields = function (language) {
    this._grammarClassSelectFld.getMenu().clearItems();
    var grammarItems = language.grammarItems;
    var items = [];

    for (var key in grammarItems) {
      if (grammarItems.hasOwnProperty(key)) {
        var grammarItem = grammarItems[key];
        items.push(new OO.ui.MenuOptionWidget({
          data: key,
          label: new OO.ui.HtmlSnippet(grammarItem.grammaticalClass.label),
        }));
      }
    }

    this._grammarClassSelectFld.getMenu().addItems(items);
    this._grammarClassSelectFld.getMenu().selectItem(items[0]);

    this._pronunciationFld.setDisabled(language.code === "conv");
  };

  // noinspection JSValidateJSDoc
  /**
   * Sets the values of the given OOUI dropdown widget.
   * @param values {Array<wikt.gadgets.creerNouveauMot.Gender|wikt.gadgets.creerNouveauMot.Number>} The list of values.
   * @param field {OO.ui.DropdownWidget} The OOUI widget.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype._setListValues = function (values, field) {
    field.getMenu().clearItems();
    var items = [];
    for (var i = 0; i < values.length; i++) {
      var value = values[i];
      items.push(new OO.ui.MenuOptionWidget({
        data: value.label,
        label: new OO.ui.HtmlSnippet(value.label),
      }));
    }
    field.getMenu().addItems(items);
    field.getMenu().selectItem(items[0]);
  };

  /**
   * Creates an HTML list of IPA symbols from a array of symbols.
   * @param ipaSymbols {Array<Array<string>>} The list of IPA symbols.
   * @return {object} A jQuery object.
   * @private
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype._formatApi = function (ipaSymbols) {
    var $label = $("<span>");

    for (var i = 0; i < ipaSymbols.length; i++) {
      $label.append(this._createLinks(ipaSymbols[i], this._pronunciationFld, "API"));
      if (i < ipaSymbols.length - 1) {
        $label.append(" &mdash; ");
      }
    }

    return $label;
  };

  /**
   * Creates an HTML links sequence that will insert text into a text field when clicked.
   * @param list {Array<string>} The list of strings to convert into links.
   * @param textField {object} The text field to insert the text into.
   * @param cssClass {string?} Optional additonnal CSS classes.
   * @param text {string?} Some text that will be appended before the links.
   * @return {Object} A jQuery object.
   * @private
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype._createLinks = function (list, textField, cssClass, text) {
    var $links = $("<span>");

    if (text) {
      $links.append(text + " &mdash; ");
    }

    for (var i = 0; i < list.length; i++) {
      var item = list[i];
      var $link = $('<a href="#" class="{0}" data-value="{1}">{2}</a>'
          .format(cssClass, item.replace("&", "&amp;"), item.trim()));
      // noinspection JSCheckFunctionSignatures
      $link.click(function (e) {
        textField.insertContent($(e.target).data("value"));
        textField.focus();
        // Return false to disable default event from triggering.
        return false;
      });
      $links.append($link);
      if (i < list.length - 1) {
        $links.append("\u00a0");
      }
    }

    return $links;
  };

  /*
   * Getters & setters
   */

  /**
   * Returns the contents of the given section.
   * @param sectionCode {string} Sections’s code.
   * @return {string} The section’s contents.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.getSectionContent = function (sectionCode) {
    // noinspection JSCheckFunctionSignatures
    return this._otherSectionFields[sectionCode].getValue().trim();
  };

  /**
   * Sets the contents of the given section.
   * @param sectionCode {string} Sections’s code.
   * @param content {string} The section’s contents.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.setSectionContent = function (sectionCode, content) {
    this._otherSectionFields[sectionCode].setValue(content.trim());
  };

  /**
   * Indicates whether a link to the given sister project has to be inserted.
   * @param projectCode {string} Project’s code.
   * @return {boolean} True if a link has to be inserted.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.hasAddLinkToProject = function (projectCode) {
    return this._seeOtherProjectsChk[projectCode]["checkbox"].isSelected();
  };

  /**
   * Sets whether a link to the given sister project has to be inserted.
   * @param projectCode {string} Project’s code.
   * @param link {boolean} True if a link has to be inserted.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.setAddLinkToProject = function (projectCode, link) {
    this._seeOtherProjectsChk[projectCode]["checkbox"].setSelected(link);
  };

  /**
   * Returns the template parameters for the given sister project link.
   * @param projectCode {string} Project’s code.
   * @return {string} Template’s parameters.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.getProjectLinkParams = function (projectCode) {
    // noinspection JSCheckFunctionSignatures
    return this._seeOtherProjectsChk[projectCode]["textfield"].getValue().trim();
  };

  /**
   * Sets template parameters for the given sister project link.
   * @param projectCode {string} Project’s code.
   * @param params {string} Template’s parameters.
   */
  wikt.gadgets.creerNouveauMot.MainGui.prototype.setProjectLinkParams = function (projectCode, params) {
    this._seeOtherProjectsChk[projectCode]["textfield"].setValue(params.trim());
  };

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "tabsNumber", {
    /**
     * @return {number} The number of tabs.
     */
    get: function () {
      return this._tabs.length;
    }
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "selectedLanguage", {
    /**
     * @return {string} Selected language’s code.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._languageSelectFld.getMenu().findSelectedItem().getData();
    }
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "gender", {
    /**
     * @return {string} Selected gender’s code.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._gendersFld.getMenu().findSelectedItem().getData();
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "number", {
    /**
     * @return {string} Selected grammatical number’s code.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._numbersFld.getMenu().findSelectedItem().getData();
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "grammarClass", {
    /**
     * @return {string} Selected grammatical class.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._grammarClassSelectFld.getMenu().findSelectedItem().getData();
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "pronunciation", {
    /**
     * @return {string} The pronunciation.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._pronunciationFld.getValue().trim();
    },

    /**
     * Sets the pronunciation.
     * @param pron {string} The pronunciation.
     */
    set: function (pron) {
      this._pronunciationFld.setValue(pron.trim());
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "definition", {
    /**
     * @return {string} The definition.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._definitionFld.getValue().trim();
    },

    /**
     * Sets the definition.
     * @param def {string} The definition.
     */
    set: function (def) {
      this._definitionFld.setValue(def.trim());
    }
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "etymology", {
    /**
     * @return {string} The etymology.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._etymologyFld.getValue().trim();
    },

    /**
     * Sets the etymology.
     * @param etym {string} The etymology.
     */
    set: function (etym) {
      this._etymologyFld.setValue(etym.trim());
    }
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "references", {
    /**
     * @return {string} The references.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._referencesFld.getValue().trim();
    },

    /**
     * Sets the references.
     * @param references {string} The references.
     */
    set: function (references) {
      this._referencesFld.setValue(references.trim());
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "sources", {
    /**
     * @return {string} The sources.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._sourcesFld.getValue().trim();
    },

    /**
     * Sets the sources.
     * @param sources {string} The sources.
     */
    set: function (sources) {
      this._sourcesFld.setValue(sources.trim());
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "bibliography", {
    /**
     * @return {string} The bibliography.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._bibliographyFld.getValue().trim();
    },

    /**
     * Sets the bibliography.
     * @param bibliography {string} The bibliography.
     */
    set: function (bibliography) {
      this._bibliographyFld.setValue(bibliography.trim());
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "isDraft", {
    /**
     * Indicates whether the article is a draft.
     * @return {boolean} True if it is a draft.
     */
    get: function () {
      return this._draftChk.isSelected();
    },

    /**
     * Sets whether the article is a draft.
     * @param draft {boolean} True if it is a draf
     */
    set: function (draft) {
      this._draftChk.setSelected(draft);
    },
  });

  Object.defineProperty(wikt.gadgets.creerNouveauMot.MainGui.prototype, "sortingKey", {
    /**
     * @return {string} The sorting key.
     */
    get: function () {
      // noinspection JSCheckFunctionSignatures
      return this._sortKeyFld.getValue().trim();
    },

    /**
     * Defines the sorting key.
     * @param key {string} The sorting key.
     */
    set: function (key) {
      this._sortKeyFld.setValue(key.trim());
    },
  });

  /**
   * This class represents a grammatical number (singular, plural, etc.).
   * @param label {string} Number’s label.
   * @param template {string?} Number’s template if any.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.Number = function (label, template) {
    this._label = label;
    this._template = template || "";
  }

  wikt.gadgets.creerNouveauMot.Number.prototype = {
    /**
     * @return {string} The label.
     */
    get label() {
      return this._label;
    },

    /**
     * @return {string} The template if any.
     */
    get template() {
      return this._template;
    },
  }

  /**
   * Defines all available grammatical numbers.
   * @type {Object<string, wikt.gadgets.creerNouveauMot.Number>}
   */
  wikt.gadgets.creerNouveauMot.numbers = {
    DIFF_SINGULAR_PLURAL: new wikt.gadgets.creerNouveauMot.Number("sing. et plur. différents"),
    SAME_SINGULAR_PLURAL: new wikt.gadgets.creerNouveauMot.Number("sing. et plur. identiques", "{{sp}}"),
    SINGULAR_ONLY: new wikt.gadgets.creerNouveauMot.Number("singulier uniquement", "{{au singulier uniquement}}"),
    PLURAL_ONLY: new wikt.gadgets.creerNouveauMot.Number("pluriel uniquement", "{{au pluriel uniquement}}"),
    INVARIABLE: new wikt.gadgets.creerNouveauMot.Number("invariable", "{{invar}}"),
  };

  /**
   * This class represents a grammatical gender (feminine, masculine, etc.).
   * @param label {string} Gender’s label.
   * @param template {string?} Gender’s template if any.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.Gender = function (label, template) {
    this._label = label;
    this._template = template || "";
  }

  wikt.gadgets.creerNouveauMot.Gender.prototype = {
    /**
     * @return {string} Gender’s label.
     */
    get label() {
      return this._label;
    },

    /**
     * @return {string} Gender’s template if any.
     */
    get template() {
      return this._template;
    },
  }

  /**
   * Defines all available grammatical genders.
   * @type {Object<string, wikt.gadgets.creerNouveauMot.Gender>}
   */
  wikt.gadgets.creerNouveauMot.genders = {
    MASCULINE: new wikt.gadgets.creerNouveauMot.Gender("masculin", "{{m}}"),
    FEMININE: new wikt.gadgets.creerNouveauMot.Gender("féminin", "{{f}}"),
    FEMININE_MASCULINE: new wikt.gadgets.creerNouveauMot.Gender("masc. et fém. identiques", "{{mf}}"),
    NO_GENDER: new wikt.gadgets.creerNouveauMot.Gender("pas de genre"),
    VERB_GROUP1: new wikt.gadgets.creerNouveauMot.Gender("1<sup>er</sup> groupe", "{{conjugaison|fr|group=1}}"),
    VERB_GROUP2: new wikt.gadgets.creerNouveauMot.Gender("2<sup>ème</sup> groupe", "{{conjugaison|fr|group=2}}"),
    VERB_GROUP3: new wikt.gadgets.creerNouveauMot.Gender("3<sup>ème</sup> groupe", "{{conjugaison|fr|group=3}}"),
    VERB: new wikt.gadgets.creerNouveauMot.Gender("verbe", "{{conjugaison|{0}}}"),
    REGULAR_VERB: new wikt.gadgets.creerNouveauMot.Gender("régulier"),
    IRREGULAR_VERB: new wikt.gadgets.creerNouveauMot.Gender("irrégulier"),
  };

  /**
   * This class represents a grammatical class.
   * @param label {string} Class’ label.
   * @param sectionCode {string} Class’ section code.
   * (as defined in [[Wiktionnaire:Structure_des_pages#Résumé_des_sections]] 2,1 onwards).
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.GrammaticalClass = function (label, sectionCode) {
    this._label = label;
    this._sectionCode = sectionCode;
  }

  wikt.gadgets.creerNouveauMot.GrammaticalClass.prototype = {
    /**
     * @return {string} Class’ label.
     */
    get label() {
      return this._label;
    },

    /**
     * @return {string} Class’ section code.
     */
    get sectionCode() {
      return this._sectionCode;
    },
  }

  /**
   * Defines all available grammatical classes.
   * @type {Object<string, wikt.gadgets.creerNouveauMot.GrammaticalClass>}
   */
  wikt.gadgets.creerNouveauMot.grammaticalClasses = {
    SYMBOL: new wikt.gadgets.creerNouveauMot.GrammaticalClass("symbole", "symbole"),
    LETTER: new wikt.gadgets.creerNouveauMot.GrammaticalClass("lettre", "lettre"),

    SCIENTIFIC_NAME: new wikt.gadgets.creerNouveauMot.GrammaticalClass("nom scientifique", "nom scientifique"),

    // Nouns
    NOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("nom commun", "nom"),
    PROPER_NOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("nom propre", "nom propre"),
    FIRST_NAME: new wikt.gadgets.creerNouveauMot.GrammaticalClass("prénom", "prénom"),
    LAST_NAME: new wikt.gadgets.creerNouveauMot.GrammaticalClass("nom de famille", "nom de famille"),

    // Adjectives
    ADJECTIVE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adjectif", "adjectif"),
    INTERROGATIVE_ADJECTIVE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adjectif interrogatif", "adjectif interrogatif"),
    NUMERAL_ADJECTIVE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adjectif numéral", "adjectif numéral"),
    POSSESSIVE_ADJECTIVE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adjectif possessif", "adjectif possessif"),

    // Adverbs
    ADVERB: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adverbe", "adverbe"),
    INTERROGATIVE_ADVERB: new wikt.gadgets.creerNouveauMot.GrammaticalClass("adverbe interrogatif", "adverbe interrogatif"),

    // Pronouns
    PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom", "pronom"),
    DEMONSTRATIVE_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom démonstratif", "pronom démonstratif"),
    INDEFINITE_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom indéfini", "pronom indéfini"),
    INTERROGATIVE_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom interrogatif", "pronom interrogatif"),
    PERSONAL_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom personnel", "pronom personnel"),
    POSSESSIVE_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom possessif", "pronom possessif"),
    RELATIVE_PRONOUN: new wikt.gadgets.creerNouveauMot.GrammaticalClass("pronom relatif", "pronom relatif"),

    // Conjunctions
    CONJUNCTION: new wikt.gadgets.creerNouveauMot.GrammaticalClass("conjonction", "conjonction"),
    COORDINATION_CONJUNCTION: new wikt.gadgets.creerNouveauMot.GrammaticalClass("conjonction de coordination", "conjonction de coordination"),

    // Articles
    ARTICLE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("article", "article"),
    INDEFINITE_ARTICLE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("article indéfini", "article indéfini"),
    DEFINITE_ARTICLE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("article défini", "article défini"),
    PARTITIVE_ARTICLE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("article partitif", "article partitif"),

    // Affixes
    PREFIX: new wikt.gadgets.creerNouveauMot.GrammaticalClass("préfixe", "préfixe"),
    SUFFIX: new wikt.gadgets.creerNouveauMot.GrammaticalClass("suffixe", "suffixe"),
    CIRCUMFIX: new wikt.gadgets.creerNouveauMot.GrammaticalClass("circonfixe", "circonfixe"),
    INFIX: new wikt.gadgets.creerNouveauMot.GrammaticalClass("infixe", "infixe"),

    VERB: new wikt.gadgets.creerNouveauMot.GrammaticalClass("verbe", "verbe"),
    PREPOSITION: new wikt.gadgets.creerNouveauMot.GrammaticalClass("préposition", "préposition"),
    POSTPOSITION: new wikt.gadgets.creerNouveauMot.GrammaticalClass("postposition", "postposition"),
    PARTICLE: new wikt.gadgets.creerNouveauMot.GrammaticalClass("particule", "particule"),
    INTERJECTION: new wikt.gadgets.creerNouveauMot.GrammaticalClass("interjection", "interjection"),
  };

  /**
   * A grammatical item associates a grammatical class to genders and numbers.
   * @param grammaticalClass {wikt.gadgets.creerNouveauMot.GrammaticalClass} The grammatical class.
   * @param availableGenders {Array<wikt.gadgets.creerNouveauMot.Gender>?} Associated genders.
   * @param availableNumbers {Array<wikt.gadgets.creerNouveauMot.Number>?} Associated numbers.
   * @param generateInflections {Function?} Optional function that generates inflections template.
   * @constructor
   */
  wikt.gadgets.creerNouveauMot.GrammaticalItem = function (grammaticalClass, availableGenders, availableNumbers, generateInflections) {
    this._grammaticalClass = grammaticalClass;
    /** @type {Array<wikt.gadgets.creerNouveauMot.Gender>} */
    this._availableGenders = availableGenders || [new wikt.gadgets.creerNouveauMot.Gender("<em>indisponible</em>")];
    /** @type {Array<wikt.gadgets.creerNouveauMot.Number>} */
    this._availableNumbers = availableNumbers || [new wikt.gadgets.creerNouveauMot.Number("<em>indisponible</em>")];
    this._generateInflections = generateInflections || function () {
      return "";
    };
  };

  wikt.gadgets.creerNouveauMot.GrammaticalItem.prototype = {
    /**
     * @return {wikt.gadgets.creerNouveauMot.GrammaticalClass} The grammatical class.
     */
    get grammaticalClass() {
      return this._grammaticalClass;
    },

    /**
     * @return {Array<wikt.gadgets.creerNouveauMot.Gender>} Associated genders.
     */
    get availableGenders() {
      return this._availableGenders;
    },

    /**
     * @return {Array<wikt.gadgets.creerNouveauMot.Number>} Associated numbers.
     */
    get availableNumbers() {
      return this._availableNumbers;
    },
  };

  /**
   * Fetches the gender with the given label.
   * @param genderLabel {string} Gender’s label.
   * @return {wikt.gadgets.creerNouveauMot.Gender|null} The gender object or null if none were found.
   */
  wikt.gadgets.creerNouveauMot.GrammaticalItem.prototype.getGender = function (genderLabel) {
    for (var i = 0; i < this._availableGenders.length; i++) {
      var gender = this._availableGenders[i];
      if (gender.label === genderLabel) {
        return gender;
      }
    }

    return null;
  };

  /**
   * Fetches the number with the given label.
   * @param numberLabel {string} Number’s label
   * @return {wikt.gadgets.creerNouveauMot.Number|null} The number object or null if none were found.
   */
  wikt.gadgets.creerNouveauMot.GrammaticalItem.prototype.getNumber = function (numberLabel) {
    for (var i = 0; i < this._availableNumbers.length; i++) {
      var number = this._availableNumbers[i];
      if (number.label === numberLabel) {
        return number;
      }
    }

    return null;
  };

  /**
   * Generates inflections template.
   * @param word {string} The base word.
   * @param genderLabel {string} Gender’s label.
   * @param numberLabel {string} Number’s label.
   * @param pronunciation {string} IPA pronunciation.
   * @return {string} Template’s wikicode.
   */
  wikt.gadgets.creerNouveauMot.GrammaticalItem.prototype.getInflectionsTemplate = function (word, genderLabel, numberLabel, pronunciation) {
    var grammarClass = this._grammaticalClass.label;
    grammarClass = grammarClass.charAt(0).toUpperCase() + grammarClass.substring(1);
    return this._generateInflections(word, grammarClass, genderLabel, numberLabel, pronunciation);
  };

  $(function () {
    // Activate only in main namespace when in edit/submit mode.
    if (wikt.page.hasNamespaceIn([""]) && ["edit", "submit"].includes(mw.config.get("wgAction"))) {
      console.log("Chargement de Gadget-CreerNouveauMot.js…");

      var namespaceId = mw.config.get("wgNamespaceIds")["mediawiki"];
      var basePage = "Gadget-CreerNouveauMot.js";

      wikt.page.getSubpages(namespaceId, basePage, "[a-zA-Z]*\\.js", function (response) {
        var modules = $.map(response.query.search, function (e) {
          return "https://fr.wiktionary.org/wiki/{0}?action=raw&ctype=text/javascript".format(e.title);
        });
        wikt.loadScripts(modules).done(function () {
          wikt.gadgets.creerNouveauMot.init();
        });
      });
    }
  });
});