Convert `Language*Form` to form builder
authormutec <mysterycode@mysterycode.de>
Sun, 29 Jan 2023 12:36:08 +0000 (13:36 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 26 Apr 2023 09:02:45 +0000 (11:02 +0200)
see #5252

[Tim: Performed some final adjustments]

wcfsetup/install/files/acp/templates/languageAdd.tpl
wcfsetup/install/files/lib/acp/form/LanguageAddForm.class.php
wcfsetup/install/files/lib/acp/form/LanguageEditForm.class.php
wcfsetup/install/files/lib/data/language/LanguageAction.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 0ab57b2dfcd40199a2ddcb962fe21e062259ee5c..7ca72d82fc959f8b2f05dbf3f5aa0020dea00dca 100644 (file)
        </nav>
 </header>
 
-{include file='formNotice'}
-
-<form method="post" action="{if $action == 'edit'}{link controller='LanguageEdit' id=$languageID}{/link}{else}{link controller='LanguageAdd'}{/link}{/if}">
-       <section class="section">
-               <dl{if $errorField == 'languageName'} class="formError"{/if}>
-                       <dt><label for="languageName">{lang}wcf.global.name{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="languageName" name="languageName" value="{$languageName}" class="long" required>
-                               {if $errorField == 'languageName'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.language.add.languageName.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                               <small>{lang}wcf.acp.language.name.description{/lang}</small>
-                       </dd>
-               </dl>
-               
-               <dl{if $errorField == 'languageCode'} class="formError"{/if}>
-                       <dt><label for="languageCode">{lang}wcf.acp.language.code{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="languageCode" name="languageCode" value="{$languageCode}" class="medium" required>
-                               {if $errorField == 'languageCode'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.language.add.languageCode.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                               <small>{lang}wcf.acp.language.code.description{/lang}</small>
-                       </dd>
-               </dl>
-               
-               <dl{if $errorField == 'countryCode'} class="formError"{/if}>
-                       <dt><label for="countryCode">{lang}wcf.acp.language.countryCode{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="countryCode" name="countryCode" value="{$countryCode}" class="medium" required>
-                               {if $errorField == 'countryCode'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.language.add.countryCode.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                               <small>{lang}wcf.acp.language.countryCode.description{/lang}</small>
-                       </dd>
-               </dl>
-               
-               <dl{if $errorField == 'locale'} class="formError"{/if}>
-                       <dt><label for="locale">{lang}wcf.acp.language.locale{/lang}</label></dt>
-                       <dd>
-                               <select id="locale" name="locale" class="medium" required>
-                                       <option value="">{lang}wcf.global.noSelection{/lang}</option>
-                                       {foreach from=$locales key='identifier' item='displayName'}
-                                               <option value="{$identifier}"{if $identifier === $locale} selected{/if}>{$displayName}</option>
-                                       {/foreach}
-                               </select>
-                               {if $errorField == 'locale'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.acp.language.add.locale.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                               <small>{lang}wcf.acp.language.locale.description{/lang}</small>
-                       </dd>
-               </dl>
-               
-               {if $action == 'add'}
-                       <dl{if $errorField == 'sourceLanguageID'} class="formError"{/if}>
-                               <dt><label for="sourceLanguageID">{lang}wcf.acp.language.add.source{/lang}</label></dt>
-                               <dd>
-                                       <select id="sourceLanguageID" name="sourceLanguageID">
-                                               {foreach from=$languages item=language}
-                                                       <option value="{$language->languageID}"{if $language->languageID == $sourceLanguageID} selected{/if}>{$language->languageName} ({$language->languageCode})</option>
-                                               {/foreach}
-                                       </select>
-                                       {if $errorField == 'sourceLanguageID'}
-                                               <small class="innerError">
-                                                       {if $errorType == 'empty'}
-                                                               {lang}wcf.global.form.error.empty{/lang}
-                                                       {else}
-                                                               {lang}wcf.acp.language.add.source.error.{@$errorType}{/lang}
-                                                       {/if}
-                                               </small>
-                                       {/if}
-                                       <small>{lang}wcf.acp.language.add.source.description{/lang}</small>
-                               </dd>
-                       </dl>
-               {/if}
-               
-               {event name='fields'}
-       </section>
-       
-       {event name='sections'}
-       
-       <div class="formSubmit">
-               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
-               {csrfToken}
-       </div>
-</form>
+{@$form->getHtml()}
 
 {include file='footer'}
index 8a1378a52cbf41e868e756b71e9e44c795e325f9..bf03b27e871b3a6958ff3a3c50e0f4e7830484f1 100644 (file)
@@ -3,82 +3,54 @@
 namespace wcf\acp\form;
 
 use wcf\data\language\Language;
-use wcf\data\language\LanguageEditor;
-use wcf\form\AbstractForm;
-use wcf\system\exception\UserInputException;
+use wcf\data\language\LanguageAction;
+use wcf\form\AbstractFormBuilderForm;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\data\processor\CustomFormDataProcessor;
+use wcf\system\form\builder\field\SingleSelectionFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\field\validation\FormFieldValidator;
+use wcf\system\form\builder\IFormDocument;
 use wcf\system\language\LanguageFactory;
-use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
-use wcf\util\StringUtil;
 
 /**
  * Shows the language add form.
  *
- * @author  Marcel Werk
- * @copyright   2001-2019 WoltLab GmbH
+ * @property    LanguageAction  $objectAction
+ *
+ * @author  Florian Gail, Marcel Werk
+ * @copyright   2001-2023 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-class LanguageAddForm extends AbstractForm
+class LanguageAddForm extends AbstractFormBuilderForm
 {
     /**
      * @inheritDoc
      */
     public $activeMenuItem = 'wcf.acp.menu.link.language.list';
 
-    /**
-     * country code
-     * @var string
-     */
-    public $countryCode = '';
-
     /**
      * @inheritDoc
      */
     public $neededPermissions = ['admin.language.canManageLanguage'];
 
     /**
-     * language object
-     * @var Language
-     */
-    public $language;
-
-    /**
-     * language name
-     * @var string
-     */
-    public $languageName = '';
-
-    /**
-     * language code
-     * @var string
-     */
-    public $languageCode = '';
-
-    /**
-     * list of available languages
-     * @var Language[]
-     */
-    public $languages = [];
-
-    /**
-     * source language object
-     * @var Language
+     * @inheritDoc
      */
-    public $sourceLanguage;
+    public $objectActionClass = LanguageAction::class;
 
     /**
-     * source language id
-     * @var int
+     * @inheritDoc
      */
-    public $sourceLanguageID = 0;
+    public $objectEditLinkController = LanguageEditForm::class;
 
     /**
      * @var string[]
      */
     public array $locales;
 
-    public string $locale = '';
-
     /**
      * @inheritDoc
      */
@@ -103,149 +75,124 @@ class LanguageAddForm extends AbstractForm
     /**
      * @inheritDoc
      */
-    public function readFormParameters()
+    protected function createForm()
     {
-        parent::readFormParameters();
-
-        if (isset($_POST['countryCode'])) {
-            $this->countryCode = StringUtil::trim($_POST['countryCode']);
-        }
-        if (isset($_POST['languageName'])) {
-            $this->languageName = StringUtil::trim($_POST['languageName']);
-        }
-        if (isset($_POST['languageCode'])) {
-            $this->languageCode = StringUtil::trim($_POST['languageCode']);
-        }
-        if (isset($_POST['sourceLanguageID'])) {
-            $this->sourceLanguageID = \intval($_POST['sourceLanguageID']);
-        }
-        if (isset($_POST['locale'])) {
-            $this->locale = $_POST['locale'];
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function validate()
-    {
-        parent::validate();
-
-        // language name
-        if (empty($this->languageName)) {
-            throw new UserInputException('languageName');
-        }
-
-        // country code
-        if (empty($this->countryCode)) {
-            throw new UserInputException('countryCode');
-        }
-
-        // language code
-        $this->validateLanguageCode();
-
-        // source language id
-        $this->validateSource();
-
-        if (!isset($this->locales[$this->locale])) {
-            throw new UserInputException('locale');
-        }
-    }
-
-    /**
-     * Validates the language code.
-     */
-    protected function validateLanguageCode()
-    {
-        if (empty($this->languageCode)) {
-            throw new UserInputException('languageCode');
-        }
-        if (LanguageFactory::getInstance()->getLanguageByCode($this->languageCode)) {
-            throw new UserInputException('languageCode', 'notUnique');
-        }
-    }
-
-    /**
-     * Validates given source language.
-     */
-    protected function validateSource()
-    {
-        if (empty($this->sourceLanguageID)) {
-            throw new UserInputException('sourceLanguageID');
-        }
-
-        // get language
-        $this->sourceLanguage = LanguageFactory::getInstance()->getLanguage($this->sourceLanguageID);
-        if (!$this->sourceLanguage->languageID) {
-            throw new UserInputException('sourceLanguageID');
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function save()
-    {
-        parent::save();
-
-        $this->language = LanguageEditor::create(\array_merge($this->additionalFields, [
-            'countryCode' => \mb_strtolower($this->countryCode),
-            'languageName' => $this->languageName,
-            'languageCode' => \mb_strtolower($this->languageCode),
-            'locale' => $this->locale,
-        ]));
-        $languageEditor = new LanguageEditor($this->sourceLanguage);
-        $languageEditor->copy($this->language);
-
-        // copy content
-        LanguageEditor::copyLanguageContent($this->sourceLanguage->languageID, $this->language->languageID);
-
-        // reset caches
-        LanguageFactory::getInstance()->clearCache();
-        LanguageFactory::getInstance()->deleteLanguageCache();
-
-        $this->saved();
-
-        // show success message
-        WCF::getTPL()->assign([
-            'success' => true,
-            'objectEditLink' => LinkHandler::getInstance()->getControllerLink(
-                LanguageEditForm::class,
-                ['id' => $this->language->languageID]
-            ),
+        parent::createForm();
+
+        $locales = [
+            '' => WCF::getLanguage()->get('wcf.global.noSelection'),
+            ...$this->locales,
+        ];
+
+        $this->form->appendChildren([
+            FormContainer::create('data')
+                ->appendChildren([
+                    TextFormField::create('languageName')
+                        ->label('wcf.global.name')
+                        ->description('wcf.acp.language.name.description')
+                        ->maximumLength(255)
+                        ->required(),
+                    TextFormField::create('languageCode')
+                        ->label('wcf.acp.language.code')
+                        ->description('wcf.acp.language.code.description')
+                        ->maximumLength(20)
+                        ->required()
+                        ->addValidator(new FormFieldValidator('unique', function (TextFormField $formField) {
+                            if ($formField->getValidationErrors() !== []) {
+                                return;
+                            }
+
+                            if (
+                                $this->formObject instanceof Language
+                                && \mb_strtolower($this->formObject->languageCode) === $formField->getValue()
+                            ) {
+                                return;
+                            }
+
+                            if (LanguageFactory::getInstance()->getLanguageByCode($formField->getValue())) {
+                                $formField->addValidationError(new FormFieldValidationError(
+                                    'notUnique',
+                                    'wcf.acp.language.add.languageCode.error.notUnique'
+                                ));
+                            }
+                        })),
+                    TextFormField::create('countryCode')
+                        ->label('wcf.acp.language.countryCode')
+                        ->description('wcf.acp.language.countryCode.description')
+                        ->maximumLength(10)
+                        ->required(),
+                    SingleSelectionFormField::create('locale')
+                        ->label('wcf.acp.language.locale')
+                        ->description('wcf.acp.language.locale.description')
+                        ->options($locales)
+                        ->addValidator(new FormFieldValidator(
+                            'locale',
+                            static function (SingleSelectionFormField $formField) {
+                                if ($formField->getValue() === '') {
+                                    return;
+                                }
+
+                                if ($formField->getValidationErrors() !== []) {
+                                    return;
+                                }
+
+                                $languageCodeField = $formField->getDocument()->getNodeById('languageCode');
+                                \assert($languageCodeField instanceof TextFormField);
+
+                                [$languageCode] = \explode('_', $formField->getValue());
+                                if ($languageCodeField->getValue() !== $languageCode) {
+                                    $formField->addValidationError(
+                                        new FormFieldValidationError(
+                                            'languageCodeMismatch',
+                                            'wcf.acp.language.add.locale.error.languageCodeMismatch',
+                                            [
+                                                'locale' => $formField->getValue(),
+                                            ]
+                                        )
+                                    );
+                                }
+                            }
+                        )),
+                    SingleSelectionFormField::create('sourceLanguageID')
+                        ->label('wcf.acp.language.add.source')
+                        ->description('wcf.acp.language.add.source.description')
+                        ->options(LanguageFactory::getInstance()->getLanguages())
+                        ->available($this->formAction === IFormDocument::FORM_MODE_CREATE)
+                        ->required(),
+                ]),
         ]);
-
-        // reset values
-        $this->countryCode = $this->languageCode = $this->languageName = $this->locale = '';
-        $this->sourceLanguageID = 0;
     }
 
     /**
      * @inheritDoc
      */
-    public function readData()
+    public function buildForm()
     {
-        parent::readData();
-
-        $this->languages = LanguageFactory::getInstance()->getLanguages();
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function assignVariables()
-    {
-        parent::assignVariables();
-
-        WCF::getTPL()->assign([
-            'countryCode' => $this->countryCode,
-            'languageName' => $this->languageName,
-            'languageCode' => $this->languageCode,
-            'sourceLanguageID' => $this->sourceLanguageID,
-            'languages' => $this->languages,
-            'locale' => $this->locale,
-            'locales' => $this->locales,
-            'action' => 'add',
-        ]);
+        parent::buildForm();
+
+        $this->form->getDataHandler()->addProcessor(new CustomFormDataProcessor(
+            'lowercase',
+            static function (IFormDocument $document, array $parameters) {
+                $parameters['data']['languageCode'] = \mb_strtolower($parameters['data']['languageCode']);
+                $parameters['data']['countryCode'] = \mb_strtolower($parameters['data']['countryCode']);
+
+                return $parameters;
+            }
+        ));
+        $this->form->getDataHandler()->addProcessor(new CustomFormDataProcessor(
+            'sourceLanguage',
+            function (IFormDocument $document, array $parameters) {
+                if ($this->formAction !== IFormDocument::FORM_MODE_CREATE) {
+                    return $parameters;
+                }
+
+                if (isset($parameters['data']['sourceLanguageID'])) {
+                    $parameters['sourceLanguageID'] = $parameters['data']['sourceLanguageID'];
+                    unset($parameters['data']['sourceLanguageID']);
+                }
+
+                return $parameters;
+            }
+        ));
     }
 }
index 71a3137c480973884951ea52f3e118c670148e2a..fc2ee83fcfc9bf903754085cf6ee1f10c0fa3236 100644 (file)
@@ -2,27 +2,26 @@
 
 namespace wcf\acp\form;
 
+use CuyZ\Valinor\Mapper\MappingError;
 use wcf\data\language\Language;
-use wcf\data\language\LanguageEditor;
-use wcf\form\AbstractForm;
+use wcf\http\Helper;
 use wcf\system\exception\IllegalLinkException;
-use wcf\system\language\LanguageFactory;
-use wcf\system\WCF;
 
 /**
  * Shows the language edit form.
  *
- * @author  Marcel Werk
- * @copyright   2001-2019 WoltLab GmbH
+ * @property    Language    $formObject
+ *
+ * @author  Florian Gail
+ * @copyright   2001-2023 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
 class LanguageEditForm extends LanguageAddForm
 {
     /**
-     * language id
-     * @var int
+     * @inheritDoc
      */
-    public $languageID = 0;
+    public $formAction = 'edit';
 
     /**
      * @inheritDoc
@@ -31,79 +30,22 @@ class LanguageEditForm extends LanguageAddForm
     {
         parent::readParameters();
 
-        if (isset($_REQUEST['id'])) {
-            $this->languageID = \intval($_REQUEST['id']);
-        }
-        $this->language = new Language($this->languageID);
-        if (!$this->language->languageID) {
+        try {
+            $queryParameters = Helper::mapQueryParameters(
+                $_GET,
+                <<<'EOT'
+                    array {
+                        id: positive-int
+                    }
+                    EOT
+            );
+            $this->formObject = new Language($queryParameters['id']);
+
+            if (!$this->formObject->getObjectID()) {
+                throw new IllegalLinkException();
+            }
+        } catch (MappingError) {
             throw new IllegalLinkException();
         }
     }
-
-    /**
-     * @inheritDoc
-     */
-    protected function validateLanguageCode()
-    {
-        if ($this->language->languageCode != \mb_strtolower($this->languageCode)) {
-            parent::validateLanguageCode();
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    protected function validateSource()
-    {
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function save()
-    {
-        AbstractForm::save();
-
-        $editor = new LanguageEditor($this->language);
-        $editor->update(\array_merge($this->additionalFields, [
-            'countryCode' => \mb_strtolower($this->countryCode),
-            'languageName' => $this->languageName,
-            'languageCode' => \mb_strtolower($this->languageCode),
-            'locale' => $this->locale,
-        ]));
-        LanguageFactory::getInstance()->clearCache();
-        $this->saved();
-
-        // show success message
-        WCF::getTPL()->assign('success', true);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function readData()
-    {
-        parent::readData();
-
-        if (!\count($_POST)) {
-            $this->countryCode = $this->language->countryCode;
-            $this->languageName = $this->language->languageName;
-            $this->languageCode = $this->language->languageCode;
-            $this->locale = $this->language->locale;
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function assignVariables()
-    {
-        parent::assignVariables();
-
-        WCF::getTPL()->assign([
-            'languageID' => $this->languageID,
-            'language' => $this->language,
-            'action' => 'edit',
-        ]);
-    }
 }
index 38ab1ee4949c51cbb148c31eeed241ec7428757f..ee70f510f4b3a07519b8b4750389f81a6246ed56 100644 (file)
@@ -6,16 +6,16 @@ use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\IToggleAction;
 use wcf\data\TDatabaseObjectToggle;
 use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
 
 /**
  * Executes language-related actions.
  *
- * @author  Alexander Ebert
- * @copyright   2001-2019 WoltLab GmbH
+ * @author  Alexander Ebert, Florian Gail
+ * @copyright   2001-2023 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  *
- * @method  Language        create()
  * @method  LanguageEditor[]    getObjects()
  * @method  LanguageEditor      getSingleObject()
  */
@@ -54,6 +54,28 @@ class LanguageAction extends AbstractDatabaseObjectAction implements IToggleActi
      */
     protected $requireACP = ['create', 'delete', 'setAsDefault', 'update'];
 
+    /**
+     * @inheritDoc
+     * @return  Language
+     */
+    public function create()
+    {
+        $object = parent::create();
+        \assert($object instanceof Language);
+
+        if (isset($this->parameters['sourceLanguageID']) && $this->parameters['sourceLanguageID']) {
+            $sourceLanguage = LanguageFactory::getInstance()->getLanguage($this->parameters['sourceLanguageID']);
+
+            LanguageEditor::copyLanguageContent($sourceLanguage->getObjectID(), $object->getObjectID());
+            (new LanguageEditor($sourceLanguage))->copy($object);
+
+            LanguageFactory::getInstance()->clearCache();
+            LanguageFactory::getInstance()->deleteLanguageCache();
+        }
+
+        return $object;
+    }
+
     /**
      * Validates permission to set a language as default.
      */
index d27649e25e8ee6a1c08b371802714701dbbbfd41..f5549b2ae57abf338f9a993c811fe5fca1f28560 100644 (file)
@@ -1012,6 +1012,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE
        <category name="wcf.acp.language">
                <item name="wcf.acp.language.add"><![CDATA[Sprache hinzufügen]]></item>
                <item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[Dieser Sprachcode wird bereits von einer anderen im System installierten Sprache verwendet.]]></item>
+               <item name="wcf.acp.language.add.locale.error.languageCodeMismatch"><![CDATA[Die ausgewählte Region (<kbd>{$locale}</kbd>) entspricht nicht dem angegebenen Sprachcode.]]></item>
                <item name="wcf.acp.language.add.source"><![CDATA[Vorlage]]></item>
                <item name="wcf.acp.language.code"><![CDATA[Sprachcode]]></item>
                <item name="wcf.acp.language.code.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den passenden Sprachcode nach <strong>ISO 639-1</strong> an. Weitere Informationen zu Sprachcodes {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} unter <a href="https://de.wikipedia.org/wiki/ISO_639-1" class="externalURL">https://de.wikipedia.org/wiki/ISO_639-1</a>.]]></item>
index a62028cf96ece8d2731093d7f653ecebf36a7d21..c3f3ec932e039aeb9221c78c6094f8ab37e0d308 100644 (file)
@@ -991,6 +991,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru
        <category name="wcf.acp.language">
                <item name="wcf.acp.language.add"><![CDATA[Add Language]]></item>
                <item name="wcf.acp.language.add.languageCode.error.notUnique"><![CDATA[The language code is already in use by an installed language.]]></item>
+        <item name="wcf.acp.language.add.locale.error.languageCodeMismatch"><![CDATA[The selected region (<kbd>{$locale}</kbd>) does not match the language code.]]></item>
                <item name="wcf.acp.language.add.source"><![CDATA[Blueprint]]></item>
                <item name="wcf.acp.language.code"><![CDATA[ISO Language-Code]]></item>
                <item name="wcf.acp.language.code.description"><![CDATA[Please enter the <strong>ISO 639-1</strong> language code, you can look up the language code at <a href="https://en.wikipedia.org/wiki/ISO_639-1" class="externalURL">https://en.wikipedia.org/wiki/ISO_639-1</a>.]]></item>