Repository: mdwikicx/CX_REL1_43_05_2025
Pull request #2: Update mdwiki
Author: login: MrIbrahem
Created: 2025-05-10T01:59:37Z | Merged: 2025-05-10T02:47:03Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Added special handling for the "mdwiki" source language throughout the translation and publishing workflow, including custom content fetching, publishing, and token management.
	- Introduced campaign tracking for translations, allowing campaign information to be passed and logged during publishing.
	- Enhanced post-publish experience for "mdwiki" translations with additional UI links for fixing references and linking to Wikidata.
- **Bug Fixes**
	- Improved error handling for OAuth session expiration and captcha errors, providing clearer messages and re-login options.
- **Improvements**
	- Adjusted language selection UI to hide English and Simple English options and improved language counting logic.
	- Enhanced detailed result logging and handling of publish responses for better transparency.
	- Added user-based bypass to skip draft restoration and conflict checks for specific admin users.
	- Added special handling for the "or" target language to streamline machine translation provider selection.
	- Added language-specific error messages for empty or error states in "mdwiki" source content fetching.
- **Chores**
	- Added support for campaign and translation type parameters from URL queries for future integrations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/src/wiki/mw/api/page.js b/app/src/wiki/mw/api/page.js
index a5475a2..8aa3757 100644
--- a/app/src/wiki/mw/api/page.js
+++ b/app/src/wiki/mw/api/page.js
@@ -17,51 +17,51 @@ const defaultThumbnailSize = 120;
  * @returns {Promise<Page[]>}
  */
 const fetchPages = (language, titles) => {
-  const params = {
-    action: "query",
-    format: "json",
-    formatversion: 2,
-    prop: "info|pageprops|pageimages|description|pageviews|langlinkscount|revisions",
-    pvipdays: 7, // Last 7 days page views
-    piprop: "thumbnail|name|original",
-    rvprop: "size",
-    pithumbsize: defaultThumbnailSize,
-    titles: titles.join("|"),
-    origin: "*",
-    redirects: true,
-  };
-
-  const mwApi = siteMapper.getApi(language);
-
-  return mwApi.get(params).then((response) => {
-    const apiResponse = response.query.pages;
-    const redirects = response.query.redirects || [];
-    const redirectMap = redirects.reduce(
-      (rMap, redirect) => ({ ...rMap, [redirect.to]: redirect.from }),
-      {}
-    );
+    const params = {
+        action: "query",
+        format: "json",
+        formatversion: 2,
+        prop: "info|pageprops|pageimages|description|pageviews|langlinkscount|revisions",
+        pvipdays: 7, // Last 7 days page views
+        piprop: "thumbnail|name|original",
+        rvprop: "size",
+        pithumbsize: defaultThumbnailSize,
+        titles: titles.join("|"),
+        origin: "*",
+        redirects: true,
+    };
 
-    // consider title normalizations to also support non normalized titles for multi-word page titles
-    // e.g. "Greenhouse_gas" instead of "Greenhouse gas"
-    const titleNormalizations = response.query.normalized || [];
-    const normalizationMap = titleNormalizations.reduce(
-      (nMap, normalization) => ({
-        ...nMap,
-        [normalization.to]: normalization.from,
-      }),
-      {}
-    );
+    const mwApi = siteMapper.getApi(language);
+
+    return mwApi.get(params).then((response) => {
+        const apiResponse = response.query.pages;
+        const redirects = response.query.redirects || [];
+        const redirectMap = redirects.reduce(
+            (rMap, redirect) => ({ ...rMap, [redirect.to]: redirect.from }),
+            {}
+        );
+
+        // consider title normalizations to also support non normalized titles for multi-word page titles
+        // e.g. "Greenhouse_gas" instead of "Greenhouse gas"
+        const titleNormalizations = response.query.normalized || [];
+        const normalizationMap = titleNormalizations.reduce(
+            (nMap, normalization) => ({
+                ...nMap,
+                [normalization.to]: normalization.from,
+            }),
+            {}
+        );
 
-    return apiResponse.map((page) => {
-      // non-normalized page titles take priority over "redirect from" titles,
-      // because they only exist in the response, when they have included in the
-      // "titles" property of the request payload
-      const _alias =
-        normalizationMap[page.title] || redirectMap[page.title] || null;
+        return apiResponse.map((page) => {
+            // non-normalized page titles take priority over "redirect from" titles,
+            // because they only exist in the response, when they have included in the
+            // "titles" property of the request payload
+            const _alias =
+                normalizationMap[page.title] || redirectMap[page.title] || null;
 
-      return new Page({ ...page, _alias });
+            return new Page({ ...page, _alias });
+        });
     });
-  });
 };
 
 /**
@@ -72,37 +72,37 @@ const fetchPages = (language, titles) => {
  * @returns {Promise<LanguageTitleGroup>}
  */
 const fetchLanguageTitles = (language, title) => {
-  const params = {
-    action: "query",
-    format: "json",
-    formatversion: 2,
-    prop: "langlinks|pageprops", // pageprops for wikidataId
-    titles: title,
-    lllimit: 500, // Max limit. We have only ~300 wikis.
-    origin: "*",
-    redirects: true,
-  };
-  const mwApi = siteMapper.getApi(language);
-
-  return mwApi.get(params).then((response) => {
-    const pages = response.query.pages;
-
-    // When invalid title is provided a dummy page is return with "missing"
-    // property equal to true. So we should check also for this one.
-    if (!pages || !pages.length || pages[0]?.missing) {
-      // Page not present
-      return null;
-    }
-    const titles = [{ lang: language, title }, ...(pages[0].langlinks || [])];
-    const wikidataId = pages[0].pageprops?.wikibase_item;
+    const params = {
+        action: "query",
+        format: "json",
+        formatversion: 2,
+        prop: "langlinks|pageprops", // pageprops for wikidataId
+        titles: title,
+        lllimit: 500, // Max limit. We have only ~300 wikis.
+        origin: "*",
+        redirects: true,
+    };
+    const mwApi = siteMapper.getApi(language);
 
-    // For test articles used in development, wikidataId will be missing. Skip
-    if (!wikidataId) {
-      return null;
-    }
+    return mwApi.get(params).then((response) => {
+        const pages = response.query.pages;
+
+        // When invalid title is provided a dummy page is return with "missing"
+        // property equal to true. So we should check also for this one.
+        if (!pages || !pages.length || pages[0]?.missing) {
+            // Page not present
+            return null;
+        }
+        const titles = [{ lang: language, title }, ...(pages[0].langlinks || [])];
+        const wikidataId = pages[0].pageprops?.wikibase_item;
 
-    return Object.freeze(new LanguageTitleGroup(wikidataId, titles));
-  });
+        // For test articles used in development, wikidataId will be missing. Skip
+        if (!wikidataId) {
+            return null;
+        }
+
+        return Object.freeze(new LanguageTitleGroup(wikidataId, titles));
+    });
 };
 
 /**
@@ -117,30 +117,30 @@ const fetchLanguageTitles = (language, title) => {
  * @returns {string[]}
  */
 const fetchLanguageLinksForLanguage = (
-  sourceLanguage,
-  targetLanguage,
-  sourceTitles
+    sourceLanguage,
+    targetLanguage,
+    sourceTitles
 ) => {
-  // e.g. https://en.wikipedia.org/w/api.php?action=query&format=json&titles=Apple|Sun|Moon&prop=langlinks&lllang=el
-  const params = {
-    action: "query",
-    format: "json",
-    formatversion: 2,
-    prop: "langlinks",
-    titles: sourceTitles.join("|"),
-    lllang: targetLanguage,
-    origin: "*",
-    redirects: true,
-  };
-  const mwApi = siteMapper.getApi(sourceLanguage);
-
-  return mwApi.get(params).then((response) => {
-    const pages = Object.values(response.query.pages);
-
-    return pages
-      .map((page) => page.langlinks?.[0]?.["*"])
-      .filter((title) => !!title);
-  });
+    // e.g. https://en.wikipedia.org/w/api.php?action=query&format=json&titles=Apple|Sun|Moon&prop=langlinks&lllang=el
+    const params = {
+        action: "query",
+        format: "json",
+        formatversion: 2,
+        prop: "langlinks",
+        titles: sourceTitles.join("|"),
+        lllang: targetLanguage,
+        origin: "*",
+        redirects: true,
+    };
+    const mwApi = siteMapper.getApi(sourceLanguage);
+
+    return mwApi.get(params).then((response) => {
+        const pages = Object.values(response.query.pages);
+
+        return pages
+            .map((page) => page.langlinks?.[0]?.["*"])
+            .filter((title) => !!title);
+    });
 };
 
 /**
@@ -154,29 +154,78 @@ const fetchLanguageLinksForLanguage = (
  * @returns {Promise<Page>}
  */
 const fetchPageContent = (
-  sourceLanguage,
-  targetLanguage,
-  sourceTitle,
-  revision = null
+    sourceLanguage,
+    targetLanguage,
+    sourceTitle,
+    revision = null
 ) => {
-  return fetchSegmentedContent(
+    return fetchSegmentedContent(
+        sourceLanguage,
+        targetLanguage,
+        sourceTitle,
+        revision
+    ).then(
+        (segmentedContent) =>
+            new Page({
+                sections:
+                    segmentedContentConverter.convertSegmentedContentToPageSections(
+                        segmentedContent,
+                        false // No need to resolve references. Content can be used as it is
+                    ),
+                content: segmentedContent,
+                pagelanguage: sourceLanguage,
+                title: sourceTitle,
+            })
+    );
+};
+
+/**
+ * Fetches segmented content of a page for given source language,
+ * target language and source title.
+ * @param {string} sourceLanguage
+ * @param {string} targetLanguage
+ * @param {string} sourceTitle
+ * @param {string|null} revision
+ * @return {Promise<String>}
+ */
+const fetchSegmentedContent_mdwiki = (
     sourceLanguage,
     targetLanguage,
     sourceTitle,
-    revision
-  ).then(
-    (segmentedContent) =>
-      new Page({
-        sections:
-          segmentedContentConverter.convertSegmentedContentToPageSections(
-            segmentedContent,
-            false // No need to resolve references. Content can be used as it is
-          ),
-        content: segmentedContent,
-        pagelanguage: sourceLanguage,
-        title: sourceTitle,
-      })
-  );
+    revision = null
+) => {
+    const title = sourceTitle.replace(/ /g, "_")
+    const sourceWikiCode = sourceLanguage;
+    const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
+
+    const cxServerParams = {
+        sourcelanguage: sourceWikiCode,
+        targetlanguage: targetWikiCode,
+        section0: 1,
+    };
+
+    // var cxServerApiURL = "https://medwiki.toolforge.org/get_html.php";
+    var cxServerApiURL = "/get_html.php";
+
+    // If revision is requested, load that revision of page.
+    if (revision) {
+        cxServerParams.revision = revision;
+    } else {
+        cxServerParams.title = title;
+    }
+    const options = {
+        method: 'GET',
+        headers: { 'Accept': 'application/json' }
+    }
+    const searchParams = new URLSearchParams(cxServerParams);
+
+    cxServerApiURL = `${cxServerApiURL}?${searchParams.toString()}`;
+
+    const result = fetch(cxServerApiURL, options)
+        .then((response) => response.json())
+        .then((response) => response.segmentedContent);
+
+    return result;
 };
 
 /**
@@ -189,37 +238,40 @@ const fetchPageContent = (
  * @return {Promise<String>}
  */
 const fetchSegmentedContent = (
-  sourceLanguage,
-  targetLanguage,
-  sourceTitle,
-  revision = null
+    sourceLanguage,
+    targetLanguage,
+    sourceTitle,
+    revision = null
 ) => {
-  const sourceWikiCode = siteMapper.getWikiDomainCode(sourceLanguage);
-  const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
-  const cxServerParams = {
-    $sourcelanguage: sourceWikiCode,
-    $targetlanguage: targetWikiCode,
-    // Manual normalisation to avoid redirects on spaces but not to break namespaces
-    $title: sourceTitle.replace(/ /g, "_"),
-  };
-
-  let relativeApiURL = "/page/$sourcelanguage/$targetlanguage/$title";
-
-  // If revision is requested, load that revision of page.
-  if (revision) {
-    cxServerParams.$revision = revision;
-    relativeApiURL += "/$revision";
-  }
-
-  // Example: https://cxserver.wikimedia.org/v2/page/en/es/Vlasovite
-  const cxServerApiURL = siteMapper.getCXServerUrl(
-    relativeApiURL,
-    cxServerParams
-  );
-
-  return fetch(cxServerApiURL)
-    .then((response) => response.json())
-    .then((response) => response.segmentedContent);
+    if (sourceLanguage === "mdwiki") {
+        return fetchSegmentedContent_mdwiki(sourceLanguage, targetLanguage, sourceTitle, revision);
+    }
+    const sourceWikiCode = siteMapper.getWikiDomainCode(sourceLanguage);
+    const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
+    const cxServerParams = {
+        $sourcelanguage: sourceWikiCode,
+        $targetlanguage: targetWikiCode,
+        // Manual normalisation to avoid redirects on spaces but not to break namespaces
+        $title: sourceTitle.replace(/ /g, "_"),
+    };
+
+    let relativeApiURL = "/page/$sourcelanguage/$targetlanguage/$title";
+
+    // If revision is requested, load that revision of page.
+    if (revision) {
+        cxServerParams.$revision = revision;
+        relativeApiURL += "/$revision";
+    }
+
+    // Example: https://cxserver.wikimedia.org/v2/page/en/es/Vlasovite
+    const cxServerApiURL = siteMapper.getCXServerUrl(
+        relativeApiURL,
+        cxServerParams
+    );
+
+    return fetch(cxServerApiURL)
+        .then((response) => response.json())
+        .then((response) => response.segmentedContent);
 };
 
 /**
@@ -230,33 +282,33 @@ const fetchSegmentedContent = (
  * @return {Promise<Page[]>}
  */
 const fetchNearbyPages = async (language) => {
-  const coords = getUserCoordinates();
-
-  if (!coords) {
-    return Promise.resolve([]);
-  }
-  const params = {
-    action: "query",
-    prop: ["pageimages", "description", "langlinks", "langlinkscount"],
-    generator: "geosearch",
-    piprop: "thumbnail",
-    pithumbsize: defaultThumbnailSize,
-    lllang: language,
-    ggscoord: coords,
-    ggsradius: 1000, // Search radius in meters
-    ggslimit: 3,
-    ggsnamespace: mw.config.get("wgNamespaceIds")[""], // Main namespace
-    format: "json",
-    formatversion: 2,
-    origin: "*",
-  };
-
-  return await siteMapper
-    .getApi(language)
-    .get(params)
-    .then((response) => response.query.pages)
-    .then((pages) => pages.map((page) => new Page(page)))
-    .catch((error) => []);
+    const coords = getUserCoordinates();
+
+    if (!coords) {
+        return Promise.resolve([]);
+    }
+    const params = {
+        action: "query",
+        prop: ["pageimages", "description", "langlinks", "langlinkscount"],
+        generator: "geosearch",
+        piprop: "thumbnail",
+        pithumbsize: defaultThumbnailSize,
+        lllang: language,
+        ggscoord: coords,
+        ggsradius: 1000, // Search radius in meters
+        ggslimit: 3,
+        ggsnamespace: mw.config.get("wgNamespaceIds")[""], // Main namespace
+        format: "json",
+        formatversion: 2,
+        origin: "*",
+    };
+
+    return await siteMapper
+        .getApi(language)
+        .get(params)
+        .then((response) => response.query.pages)
+        .then((pages) => pages.map((page) => new Page(page)))
+        .catch((error) => []);
 };
 
 /**
@@ -271,40 +323,40 @@ const fetchNearbyPages = async (language) => {
  * @return {Promise<Page[]>}
  */
 const searchPagesByTitlePrefix = (query, language) => {
-  const titleQuery = query.trim();
-  const params = {
-    action: "query",
-    generator: "prefixsearch",
-    gpssearch: titleQuery,
-    prop: "pageimages|description|langlinkscount",
-    piprop: "thumbnail",
-    pithumbsize: defaultThumbnailSize,
-    pilimit: 10,
-    format: "json",
-    formatversion: 2,
-    origin: "*",
-  };
-
-  return siteMapper
-    .getApi(language)
-    .get(params)
-    .then((response) => response.query?.pages || [])
-    .then((pages) =>
-      pages
-        .sort((page1, page2) => page1.index - page2.index)
-        .map(
-          (page) => new Page(Object.assign(page, { pagelanguage: language }))
+    const titleQuery = query.trim();
+    const params = {
+        action: "query",
+        generator: "prefixsearch",
+        gpssearch: titleQuery,
+        prop: "pageimages|description|langlinkscount",
+        piprop: "thumbnail",
+        pithumbsize: defaultThumbnailSize,
+        pilimit: 10,
+        format: "json",
+        formatversion: 2,
+        origin: "*",
+    };
+
+    return siteMapper
+        .getApi(language)
+        .get(params)
+        .then((response) => response.query?.pages || [])
+        .then((pages) =>
+            pages
+                .sort((page1, page2) => page1.index - page2.index)
+                .map(
+                    (page) => new Page(Object.assign(page, { pagelanguage: language }))
+                )
         )
-    )
-    .catch((error) => []);
+        .catch((error) => []);
 };
 
 export default {
-  fetchPages,
-  fetchLanguageTitles,
-  fetchPageContent,
-  fetchSegmentedContent,
-  fetchNearbyPages,
-  searchPagesByTitlePrefix,
-  fetchLanguageLinksForLanguage,
+    fetchPages,
+    fetchLanguageTitles,
+    fetchPageContent,
+    fetchSegmentedContent,
+    fetchNearbyPages,
+    searchPagesByTitlePrefix,
+    fetchLanguageLinksForLanguage,
 };
diff --git a/includes/ActionApi/ApiContentTranslationPublish.php b/includes/ActionApi/ApiContentTranslationPublish.php
index c99f44c..f0a0c1b 100644
--- a/includes/ActionApi/ApiContentTranslationPublish.php
+++ b/includes/ActionApi/ApiContentTranslationPublish.php
@@ -42,6 +42,52 @@
 use Wikimedia\ParamValidator\TypeDef\IntegerDef;
 use Wikimedia\Stats\IBufferingStatsdDataFactory;
 
+function post_to_target($params)
+{
+	// $url = 'https://mdwiki.toolforge.org/Translation_Dashboard/publish/main.php';
+	$url = 'https://mdwiki.toolforge.org/publish/main.php';
+	$ch = curl_init();
+
+	// if ($ch === false) {
+	// 	throw new \RuntimeException('curl_init() failed');
+	// }
+
+	$usr_agent = "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)";
+
+	curl_setopt($ch, CURLOPT_URL, $url);
+	curl_setopt($ch, CURLOPT_POST, 1);
+	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
+
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+	// mdwiki_result: {"response":"Requests must have a user agent"}
+	curl_setopt($ch, CURLOPT_USERAGENT, $usr_agent);
+	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 15);
+
+	$response = curl_exec($ch);
+
+	$curlErr   = curl_error($ch);
+	$status    = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
+	curl_close($ch);
+
+	if ($response === false) {
+		return ['error' => $curlErr ?: 'Unknown cURL error', 'response' => $response];
+	}
+
+	if ($status !== 200) {
+		return ['error' => "Unexpected HTTP status $status", 'response' => $response];
+	}
+
+	$js = json_decode($response, true);
+
+	if ($js === null) {
+		return ['error' => 'Invalid JSON', 'response' => $response];
+	}
+
+	return $js ?? ['response' => $response];
+}
+
 class ApiContentTranslationPublish extends ApiBase
 {
 
@@ -73,13 +119,35 @@ public function __construct(
 		$this->translationStore = $translationStore;
 		$this->targetUrlCreator = $targetUrlCreator;
 		$this->changeTagsStore = $changeTagsStore;
+		$this->published_to = "local";
 	}
 
 	protected function getParsoidClient(): ParsoidClient
 	{
 		return $this->parsoidClientFactory->createParsoidClient();
 	}
+	protected function publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name)
+	{
+		$t_Params = [
+			'title' => $title->getPrefixedDBkey(),
+			'revid' => $sourceRevisionId,
+			'text' => $wikitext,
+			'user' => $user_name,
+			'summary' => $summary,
+			'target' => $params['to'],
+			'campaign' => $params['campaign'] ?? '',
+			'sourcetitle' => $params['sourcetitle'],
+		];
+
+		// wpCaptchaId, wpCaptchaWord
+		if (isset($params['wpCaptchaId'])) {
+			$t_Params['wpCaptchaId'] = $params['wpCaptchaId'];
+			$t_Params['wpCaptchaWord'] = $params['wpCaptchaWord'];
+		}
 
+		$mdwiki_result = post_to_target($t_Params);
+		return $mdwiki_result;
+	}
 	protected function saveWikitext($title, $wikitext, $params)
 	{
 		$categories = $this->getCategories($params);
@@ -93,19 +161,41 @@ protected function saveWikitext($title, $wikitext, $params)
 			$wikitext .= $categoryText;
 		}
 
-		$sourceLink = '[[:' . Sitemapper::getDomainCode($params['from'])
+		$wikitext = trim($wikitext);
+
+		$sourceRevisionId = $this->translation->translation['sourceRevisionId'];
+
+		$sourceLink = '[[:' . SiteMapper::getDomainCode($params['from'])
 			. ':Special:Redirect/revision/'
-			. $this->translation->translation['sourceRevisionId']
-			. '|' . $params['sourcetitle'] . ']]';
+			. $sourceRevisionId
+			. '|' . $params['sourcetitle'] . ']] to:' . $params['to'] . " #mdwikicx";
 
 		$summary = $this->msg(
 			'cx-publish-summary',
 			$sourceLink
 		)->inContentLanguage()->text();
 
+		$user_name = $this->getUser()->getName();
+
+		if (isset($params['user']) && $params['user'] != '') {
+			$user_name = $params['user'];
+		}
+
+		$mdwiki_result = false;
+
+		if ($params['from'] === "mdwiki") { #$mdwiki_result
+
+			$mdwiki_result = $this->publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name);
+			$this->published_to = "mdwiki";
+			// return $Result;
+
+			$wikitext = "<pre>$wikitext</pre>";
+			$wikitext .= "\n{{tr|" . $params['to'] . '|' . $params['sourcetitle'] . '|' . $user_name . '}}';
+		}
+
 		$apiParams = [
 			'action' => 'edit',
-			'title' => $title->getPrefixedDBkey(),
+			'title' => ($params['from'] === 'mdwiki') ? $params['to'] . "/" . $params['sourcetitle'] : $title->getPrefixedDBkey(),
 			'text' => $wikitext,
 			'summary' => $summary,
 		];
@@ -122,8 +212,13 @@ protected function saveWikitext($title, $wikitext, $params)
 		);
 
 		$api->execute();
+		$result = $api->getResult()->getResultData();
+		// if ( $params['from'] === "mdwiki") return $mdwiki_result;
 
-		return $api->getResult()->getResultData();
+		return [
+			'result' => $result,
+			'mdwiki_result' => $mdwiki_result,
+		];
 	}
 
 	protected function getTags(array $params)
@@ -263,30 +358,51 @@ public function publish()
 			);
 		}
 
-		$saveresult = $this->saveWikitext($targetTitle, $wikitext, $params);
-		$editStatus = $saveresult['edit']['result'];
+		$save_result_all = $this->saveWikitext($targetTitle, $wikitext, $params);
+
+		$saveresult = $save_result_all['result'] ?? [];
+		$saveresult_mdwiki = $save_result_all['mdwiki_result'] ?? [];
+
+		if ($params['from'] === "mdwiki") {
+			$saveresult = $saveresult_mdwiki;
+		};
+
+		$save_edit = $saveresult['edit'] ?? [];
+		$editStatus = $saveresult['edit']['result'] ?? [];
 
 		if ($editStatus === 'Success') {
-			if (isset($saveresult['edit']['newrevid'])) {
+			if (isset($save_edit['newrevid'])) {
 				$tags = $this->getTags($params);
 				// Add the tags post-send, after RC row insertion
-				$revId = intval($saveresult['edit']['newrevid']);
+				$revId = intval($save_edit['newrevid']);
 				DeferredUpdates::addCallableUpdate(function () use ($revId, $tags) {
 					$this->changeTagsStore->addTags($tags, null, $revId, null);
 				});
 			}
+			$title2 = $targetTitle->getPrefixedDBkey();
 
-			$targetURL = $this->targetUrlCreator->createTargetUrl($targetTitle->getPrefixedDBkey(), $params['to']);
+			if ($params['from'] === "mdwiki") {
+				$title2 = $params['to'] . "/" . $params['sourcetitle'];
+			};
+
+			$targetURL = $this->targetUrlCreator->createTargetUrl($title2, $params['to']);
+			$targeturl_wiki = SiteMapper::getPageURL($params['to'], $targetTitle->getPrefixedDBkey());
 			$result = [
 				'result' => 'success',
-				'targeturl' => $targetURL
+				'targeturl' => $targetURL,
+				'targeturl_wiki' => $targeturl_wiki,
+				'published_to' => $this->published_to
 			];
 
+			if (is_array($saveresult) && isset($saveresult['LinkToWikidata'])) {
+				$result['LinkToWikidata'] = $saveresult['LinkToWikidata'];
+			};
+
 			$this->translation->translation['status'] = TranslationStore::TRANSLATION_STATUS_PUBLISHED;
 			$this->translation->translation['targetURL'] = $targetURL;
 
-			if (isset($saveresult['edit']['newrevid'])) {
-				$result['newrevid'] = intval($saveresult['edit']['newrevid']);
+			if (isset($save_edit['newrevid'])) {
+				$result['newrevid'] = intval($save_edit['newrevid']);
 				$this->translation->translation['targetRevisionId'] = $result['newrevid'];
 			}
 
@@ -298,9 +414,10 @@ public function publish()
 		} else {
 			$result = [
 				'result' => 'error',
-				'edit' => $saveresult['edit']
+				'edit' => $save_edit ?? []
 			];
 		}
+		$result['save_result_all'] = $save_result_all;
 
 		$this->getResult()->addValue(null, $this->getModuleName(), $result);
 	}
@@ -360,6 +477,7 @@ public function getAllowedParams()
 				ParamValidator::PARAM_ISMULTI => true,
 			],
 			/** @todo These should be renamed to something all-lowercase and lacking a "wp" prefix */
+			'campaign' => null,
 			'wpCaptchaId' => null,
 			'wpCaptchaWord' => null,
 			'cxversion' => [
diff --git a/modules/base/mw.cx.SiteMapper.js b/modules/base/mw.cx.SiteMapper.js
index 567edff..8cfa8dc 100644
--- a/modules/base/mw.cx.SiteMapper.js
+++ b/modules/base/mw.cx.SiteMapper.js
@@ -82,12 +82,11 @@ mw.cx.SiteMapper.prototype.getLanguageCodeForWikiDomain = function (domain, fall
  */
 mw.cx.SiteMapper.prototype.getApi = function (language, options) {
 	const domain = this.getWikiDomainCode(language);
-	var url;
-	if (language === 'mdwiki') {
-		url = this.SiteTemplates_mdwiki.api;
-	} else {
-		url = this.siteTemplates.api.replace('$1', domain);
-	}
+
+	const url = language === 'mdwiki'
+		? this.SiteTemplates_mdwiki.api
+		: this.siteTemplates.api.replace('$1', domain);
+
 	options = Object.assign({ anonymous: true }, options);
 	return new mw.ForeignApi(url, options);
 };
@@ -128,10 +127,10 @@ mw.cx.SiteMapper.prototype.getPageUrl = function (language, title, params, hash)
 	if (this.isMobileDomain()) {
 		prefix += '.m';
 	}
-	var templates = this.siteTemplates;
-	if (language === 'mdwiki') {
-		templates = this.SiteTemplates_mdwiki;
-	}
+	const templates = language === 'mdwiki'
+		? this.SiteTemplates_mdwiki
+		: this.siteTemplates;
+
 	let base = templates.view;
 	if (params && Object.keys(params).length > 0) {
 		base = templates.action || templates.view;
@@ -175,8 +174,10 @@ mw.cx.SiteMapper.prototype.getCXServerUrl = function (module, params) {
 		cxserverURL = cxserverURL.replace('v1', 'v2');
 	}
 	// if module has /mdwiki then replace it with /en
-	if (module.indexOf('/mdwiki') > -1) {
-		module = module.replace('/mdwiki', '/en');
+	// if (module.indexOf('/mdwiki') > -1) {
+		// module = module.replace('/mdwiki', '/en');
+	if (module.endsWith('/mdwiki')) {
+		module = module.replace(/\/mdwiki$/, '/en');
 	}
 	return cxserverURL + module;
 };
@@ -208,8 +209,7 @@ mw.cx.SiteMapper.prototype.getLanguagePairs = function () {
 			.then((response) => response.json())
 			.then((response) => ({
 				targetLanguages: response.target,
-				sourceLanguages: ["en", "ar"],
-				// sourceLanguages: response.source,
+				sourceLanguages: response.source,
 			}))
 			.catch((response) => {
 				mw.log(
diff --git a/modules/mw.cx.MachineTranslationManager.js b/modules/mw.cx.MachineTranslationManager.js
index a743a81..eeddca5 100644
--- a/modules/mw.cx.MachineTranslationManager.js
+++ b/modules/mw.cx.MachineTranslationManager.js
@@ -21,6 +21,9 @@ mw.cx.MachineTranslationManager = function MwCxMachineTranslationManager(
 	sourceLanguage, targetLanguage, MTService
 ) {
 	this.sourceLanguage = sourceLanguage;
+
+	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+	// TODO: mdwiki work
 	this.targetLanguage = targetLanguage;
 	this.MT = MTService;
 };
diff --git a/modules/mw.cx.MachineTranslationService.js b/modules/mw.cx.MachineTranslationService.js
index c9c7ff1..17db3b2 100644
--- a/modules/mw.cx.MachineTranslationService.js
+++ b/modules/mw.cx.MachineTranslationService.js
@@ -15,6 +15,10 @@ mw.cx.MachineTranslationService = function MwCxMachineTranslationService(
 	sourceLanguage, targetLanguage, siteMapper
 ) {
 	this.sourceLanguage = sourceLanguage;
+
+	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+	// TODO: mdwiki work
+
 	this.targetLanguage = targetLanguage;
 	this.siteMapper = siteMapper;
 
@@ -148,6 +152,12 @@ mw.cx.MachineTranslationService.prototype.fetchProviders = function () {
 		$to: this.targetLanguage
 	});
 
+	// {"mt":["MinT","Google"]}
+	// 2024 By: Ibrahem Qasim
+	if (this.targetLanguage === "or") {
+		return $.Deferred().resolve(["Google"]);
+	}
+
 	return $.get(fetchProvidersUrl).then(function (response) {
 		return response.mt || [];
 	});
@@ -158,7 +168,97 @@ mw.cx.MachineTranslationService.prototype.fetchProvidersError = function () {
 	mw.log.error('[CX]', 'Unable to fetch machine translation providers.', arguments);
 };
 
+mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
+
+	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
+
+	// wait 2 seconds then do the next line
+
+	setTimeout(function () {
+		$('.cx-message-widget-message').append(
+			$('<a>')
+				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+				.attr('target', '_blank')
+				.text('Translation Dashboard')
+		);
+		$('.cx-message-widget-details').text(" Then refresh the page");
+
+	}, 2000)
+
+}
+
+mw.cx.MachineTranslationService.prototype.fetchCXServerToken_issue = function () {
+	// cxtoken
+
+	if (this.sourceLanguage === "mdwiki") {
+		var params = {
+			user: mw.user.getName(),
+			wiki: this.targetLanguage,
+			ty: "cxtoken",
+		}
+		const options = {
+			method: 'GET',
+			headers: { 'Accept': 'application/json' }
+		}
+
+		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
+
+		const result = fetch(url, options)
+			.then((response) => {
+				if (!response.ok) {
+					console.error('[CX] Error fetching mdwiki token:', response.status, response.statusText);
+					return response.json();
+				}
+				return response.json();
+			})
+			.then((data) => {
+				if (data.error && data.error.code === "no access") {
+					// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+					mw.cx.MachineTranslationService.prototype.cxtoken_error();
+					console.log('Fetch failed:', data);
+				} else {
+					console.log('Fetch successful:', data);
+				}
+			})
+			.catch(error => {
+				console.error('Error fetching mdwiki token:', error);
+			})
+		return result;
+	}
+
+	return new mw.Api().postWithToken('csrf', {
+		action: 'cxtoken',
+		assert: 'user'
+	});
+};
+
 mw.cx.MachineTranslationService.prototype.fetchCXServerToken = function () {
+	// cxtoken
+
+	if (this.sourceLanguage === "mdwiki") {
+		var params = {
+			user: mw.user.getName(),
+			wiki: this.targetLanguage,
+			ty: "cxtoken",
+		}
+		const options = {
+			method: 'GET',
+			headers: { 'Accept': 'application/json' }
+		}
+
+		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
+
+		const result = fetch(url, options)
+			.then((response) => response.json())
+			.catch(error => {
+				console.error('Error fetching mdwiki token:', error);
+			});
+
+		return result;
+	}
+
 	return new mw.Api().postWithToken('csrf', {
 		action: 'cxtoken',
 		assert: 'user'
@@ -232,3 +332,4 @@ mw.cx.MachineTranslationService.prototype.fetchTranslation = function (content,
 		return response.contents;
 	});
 };
+
diff --git a/modules/mw.cx.MwApiRequestManager.js b/modules/mw.cx.MwApiRequestManager.js
index 98a96f4..3cb1780 100644
--- a/modules/mw.cx.MwApiRequestManager.js
+++ b/modules/mw.cx.MwApiRequestManager.js
@@ -22,6 +22,8 @@
  */
 mw.cx.MwApiRequestManager = function MwCxMwApiRequestManager( sourceLanguage, targetLanguage, siteMapper ) {
 	this.sourceLanguage = sourceLanguage;
+	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+	// TODO: mdwiki work
 	this.targetLanguage = targetLanguage;
 	this.siteMapper = siteMapper;
 	this.titlePairCache = {};
diff --git a/modules/mw.cx.TargetArticle.js b/modules/mw.cx.TargetArticle.js
index 409c324..e75cc13 100644
--- a/modules/mw.cx.TargetArticle.js
+++ b/modules/mw.cx.TargetArticle.js
@@ -8,12 +8,16 @@
  * @param {ve.init.mw.CXTarget} veTarget
  * @param {Object} config Translation configuration
  * @param {mw.cx.SiteMapper} config.siteMapper SiteMapper instance
+ * @param {string} config.campaign Campaign name for targeting purposes
  */
 mw.cx.TargetArticle = function MWCXTargetArticle(translation, veTarget, config) {
 	this.translation = translation;
 	this.veTarget = veTarget;
 	this.config = config;
 	this.siteMapper = config.siteMapper;
+	this.campaign = config.campaign || '';;
+
+	console.log('MWCXTargetArticle: campaign: ' + this.campaign);
 	this.sourceTitle = translation.getSourceTitle();
 	this.sourceLanguage = translation.getSourceLanguage();
 	this.targetLanguage = translation.getTargetLanguage();
@@ -144,7 +148,9 @@ mw.cx.TargetArticle.prototype.publish = function (hasIssues, hasTooMuchUnmodifie
 			to: this.targetLanguage,
 			sourcetitle: this.sourceTitle,
 			title: this.getTargetTitle(),
+			// user: mw.user.getName(),
 			html,
+			campaign: this.campaign,
 			categories: this.getTargetCategories(hasTooMuchUnmodifiedText),
 			publishtags: this.getTags(hasTooMuchUnmodifiedText),
 			wpCaptchaId: this.captcha && this.captcha.id,
@@ -208,13 +214,46 @@ mw.cx.TargetArticle.prototype.publishSection = function () {
 mw.cx.TargetArticle.prototype.publishSuccess = function (response, jqXHR) {
 	const publishAction = this.translation.isSectionTranslation() ? 'cxpublishsection' : 'cxpublish';
 	const publishResult = response[publishAction];
+	console.log("publishResult:");
 
+	if (publishResult.save_result_all) {
+		console.log("_____");
+		console.log("local result: " + JSON.stringify(publishResult.save_result_all.result));
+		console.log("mdwiki_result: " + JSON.stringify(publishResult.save_result_all.mdwiki_result));
+	} else {
+		console.log(JSON.stringify(publishResult));
+	}
+	// {"result":"error","edit":{"error":"noaccess","username":"Mr. Ibrahem"}}
 	if (publishResult.result === 'success') {
-		this.translation.setTargetURL(publishResult.targeturl);
-		return this.publishComplete(publishResult.targettitle || null);
+		var targeturl = publishResult.targeturl;
+		if (this.sourceLanguage === "mdwiki" && publishResult.published_to != "local") {
+			targeturl = publishResult.targeturl_wiki;
+		}
+		var qid = "";
+		var wd_result = "";
+		if (publishResult.LinkToWikidata) {
+			qid = publishResult.LinkToWikidata.qid;
+			console.log('LinkToWikidata: ' + JSON.stringify(publishResult.LinkToWikidata));
+			// LinkToWikidata: {"result":"success","qid":"Q474070"}
+			wd_result = publishResult.LinkToWikidata.result;
+		}
+
+		this.translation.setTargetURL(targeturl);
+
+		var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+		// mdwiki_result: {"warnings":{"main":{"*":"Unrecognized parameters: wpCaptchaId, wpCaptchaWord."}},"edit":{"new":"","result":"Success","pageid":9895285,"title":"مستخدم:Mr. Ibrahem/أوبلتوكسيماب","contentmodel":"wikitext","oldrevid":0,"newrevid":69736856,"newtimestamp":"2025-03-02T00:36:55Z","watched":""},"LinkToWikidata":{"error":"Cannot create link for namespace:2","nserror":"","qid":"Q7876570"}}
+
+		var done = this.publishComplete(new_title || null);
+
+		if (this.sourceLanguage === "mdwiki") {
+			var title2 = new_title || this.getTargetTitle()
+			mw.cx.TargetArticle.prototype.addMdwikiLinks(this.targetLanguage, title2, qid, wd_result)
+		}
+
+		return done;
 	}
 
-	if (publishResult.edit.captcha) {
+	if (publishResult && publishResult.edit && publishResult.edit.captcha) {
 		// If there is a captcha challenge, get the solution and retry.
 		return this.loadCaptchaDialog().then(
 			this.showErrorCaptcha.bind(this, publishResult.edit.captcha)
@@ -266,6 +305,28 @@ mw.cx.TargetArticle.prototype.publishFail = function (errorCode, messageOrFailOb
 		this.getTargetTitle(),
 		data
 	);
+	let mddx = "OAuth session expired, Please Log again to Translation Dashboard";
+	// cx-message-widget-message
+	let mddxlink = "OAuth session expired, Please Log again to <a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	// {"result":"error","edit":{"error":"noaccess","username":"Mr. Ibrahem"}}
+	if (data.edit.error) {
+		if (data.edit.error === 'noaccess' || (data.edit.error && data.edit.error.code === 'noaccess')) {
+			this.showPublishError(mddx, "no access_keys in Translation_Dashboard");
+			// $('.cx-message-widget-message').html(mddxlink)
+			$('.cx-message-widget-message')
+				.empty()
+				.append(
+					$('<span>').text('OAuth session expired, Please Log again in '),
+					$('<a>')
+						.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+						.attr('target', '_blank')
+						.text('Translation Dashboard')
+				);
+			$('.cx-message-widget-details').html(" Then refresh the page");
+			// $('.cx-message-widget-details').html("<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>")
+			return;
+		}
+	}
 
 	const editError = data.error;
 	if (editError) {
@@ -370,7 +431,10 @@ mw.cx.TargetArticle.prototype.showErrorCaptcha = function (apiResult) {
 		// Based on FancyCaptcha::getFormInformation() (https://git.io/v6mml) and
 		// ext.confirmEdit.fancyCaptcha.js in the ConfirmEdit extension.
 		mw.loader.load('ext.confirmEdit.fancyCaptcha');
-		this.captchaDialog.setFancyCaptcha(apiResult.url);
+
+		let wikiurl = "https://" + this.targetLanguage + ".wikipedia.org";
+		this.captchaDialog.setFancyCaptcha(wikiurl + apiResult.url);
+
 	} else if (apiResult.type === 'simple' || apiResult.type === 'math') {
 		// SimpleCaptcha and MathCaptcha
 		this.captchaDialog.setCaptcha('captcha-create', apiResult.question, apiResult.mime);
@@ -619,3 +683,23 @@ mw.cx.TargetArticle.prototype.getTags = function (hasTooMuchUnmodifiedText) {
 
 	return tagString;
 };
+
+mw.cx.TargetArticle.prototype.addMdwikiLinks = function (targetLanguage, targetTitle, qid, wd_result) {
+	const pp = {
+		lang: targetLanguage,
+		title: targetTitle,
+		save: 1
+	};
+	var url = "https://mdwiki.toolforge.org/fixwikirefs.php?" + $.param(pp);
+	let link = `<a href='${url}' target='_blank'>Fix References</a>`
+
+	var wdlink = "";
+	if (qid != "" && qid != "undefined" && wd_result != "success") {
+		var target_wiki = targetLanguage + "wiki"
+		var wdurl = `https://www.wikidata.org/wiki/Special:SetSiteLink/${qid}/${target_wiki}?` + $.param({ page: targetTitle });
+		wdlink = ` - <a href='${wdurl}' target='_blank'>Link to Wikidata</a>`
+	}
+	var html = `<div style="float:left;">${link}${wdlink}</div>`;
+
+	$('.cx-message-widget-details').append(html);
+}
diff --git a/modules/mw.cx.TranslationController.js b/modules/mw.cx.TranslationController.js
index 014b1b1..1e8a298 100644
--- a/modules/mw.cx.TranslationController.js
+++ b/modules/mw.cx.TranslationController.js
@@ -14,6 +14,7 @@
 mw.cx.TranslationController = function MwCxTranslationController(
 	translation, veTarget, siteMapper, config
 ) {
+	this.config = config;
 	this.translation = translation;
 	this.veTarget = veTarget;
 	this.siteMapper = siteMapper;
@@ -41,7 +42,8 @@ mw.cx.TranslationController = function MwCxTranslationController(
 	}
 
 	this.targetArticle = new mw.cx.TargetArticle( this.translation, this.veTarget, {
-		siteMapper: this.siteMapper
+		siteMapper: this.siteMapper,
+		campaign: this.config?.campaign || ''
 	} );
 	this.translationTracker = new mw.cx.TranslationTracker( this.veTarget, config );
 	this.saveScheduler = OO.ui.debounce( this.processSaveQueue.bind( this ), 5 * 1000 );
diff --git a/modules/mw.cx.TranslationMdwiki.js b/modules/mw.cx.TranslationMdwiki.js
new file mode 100644
index 0000000..8be17a4
--- /dev/null
+++ b/modules/mw.cx.TranslationMdwiki.js
@@ -0,0 +1,360 @@
+//
+const mdwiki_last_url = { url: "" };
+
+const ENDPOINTS = {
+	mdwiki: "https://mdwiki.org",
+	mdwiki_wmcloud: "https://mdwiki.wmcloud.org",
+	medwikiToolforge: "https://medwiki.toolforge.org",
+	local: "http://localhost:8000"
+}
+
+function get_last_url() {
+	return mdwiki_last_url.url;
+}
+
+function createCategoryObject(cat) {
+	console.log("createCategoryObject:", cat);
+	return {
+		adapted: true,
+		sourceTitle: `Category:${cat}`,
+		targetTitle: `Jamii:${cat}`
+	};
+}
+
+// Utility to detect Swahili categories in HTML
+async function add_sw_categories(html) {
+	const categories = [];
+	const regexInfoboxDrug = /infobox drug/i;
+	const regexInfoboxMedicalCondition = /infobox medical condition/i;
+
+	// if html has "infobox drug" categories.push( createCategoryObject("Madawa") );
+	// if html has "infobox medical condition" categories.push( createCategoryObject("Magonjwa") );
+
+	if (regexInfoboxDrug.test(html)) categories.push(createCategoryObject("Madawa"));
+
+	if (regexInfoboxMedicalCondition.test(html)) categories.push(createCategoryObject("Magonjwa"));
+
+	console.log(JSON.stringify(categories));
+
+	console.log("add_sw_categories. Done");
+
+	return categories;
+}
+
+async function fetchJson(url, options) {
+	mdwiki_last_url.url += url + "\n";
+	// ---
+	console.log("fetchJson:", url);
+	// ---
+	if (!options) {
+		options = {
+			method: "GET",
+			// mode: "cors", // السماح بطلب CORS
+			dataType: "json",
+		}
+	}
+	// ---
+	options.headers = {
+		"Content-Type": "application/json",
+		"User-Agent": "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)",
+		...(options.headers ?? {})
+	};
+	// ---
+	try {
+		const response = await fetch(url, options);
+		if (!response.ok) {
+			console.error("Fetch GET error:", response.status, response.statusText, url);
+			return false;
+		}
+		return await response.json();
+	} catch (error) {
+		console.error("Fetch Error: ", error, url);
+		return false;
+	}
+}
+
+async function postJson(end_point, data) {
+	const options = {
+		method: "POST",
+		dataType: "json",
+		// mode: "no-c",
+		body: JSON.stringify(data)
+	};
+	return await fetchJson(end_point, options);
+}
+
+async function get_mdtexts_2024(title) {
+	let sanitizedTitle = title.replace(/['" :/]/g, "_");
+	// ---
+	let host = (window.location.hostname === "mdwikicx.toolforge.org") ? window.location.hostname : "medwiki.toolforge.org";
+	const url = `https://${host}/mdtexts/segments.php?title=${sanitizedTitle}`;
+	// ---
+	const data = await fetchJson(url);
+	// ---
+	if (!data) {
+		console.log("get_mdtexts_2024: not found");
+		return false;
+	}
+	// ---
+	const out = {
+		sourceLanguage: "mdwiki",
+		title: title,
+		revision: "5200",
+		segmentedContent: "",
+		categories: []
+	}
+	// ---
+	out.segmentedContent = removeUnlinkedWikibase(data.html);
+	// ---
+	const html = data.html.replaceAll("&#34;", '"');
+	const matches = html.match(/Mdwiki_revid"\},"params":\{"1":\{"wt":"(\d+)"\}\}/);
+	// ---
+	// if (matches && matches[1]) {
+	if (matches?.[1]) {
+		out.revision = matches[1];
+		console.log("get_mdtexts_2024 ", out.revision);
+	}
+	// ---
+	return out;
+}
+
+function isMedwikiHost() {
+	return window.location.hostname === "medwiki.toolforge.org";
+}
+
+function shouldUse2025() {
+	var use2025 = window.location.hostname === "mdwikicx.toolforge.org";
+	// ---
+	// if server == "localhost" then use2025 = false
+	// if (window.location.hostname === "localhost") use2025 = false;
+	// ---
+	// if (user_name === "Mr. Ibrahem" || user_name === "Mr. Ibrahem 1") use2025 = true;
+	// ---
+	return use2025;
+}
+
+// Convert HTML to segmented content
+async function HtmltoSegments(text) {
+	const url = "https://ncc2c.toolforge.org/HtmltoSegments";
+
+	// if (window.location.hostname === "localhost") {
+	// 	url = "http://localhost:8000/HtmltoSegments";
+	// }
+
+	const segmentedReq = await postJson(url, { html: text });
+
+	// Handle the response from your API
+	if (!segmentedReq || segmentedReq.error) {
+		console.error("HtmltoSegments Error:", segmentedReq?.error || "unknown error");
+		return "";
+	}
+	const segmentedContent = segmentedReq.result || '';
+	return segmentedContent;
+}
+
+// Fetch HTML from medwiki
+async function getMedwikiHtml(title, tr_type) {
+	title = title.replace(/\s/g, "_");
+
+	const { end_point, title: formattedTitle } = get_endpoint_and_title(tr_type, title);
+
+	const encodedTitle = encodeURIComponent(formattedTitle).replace(/\//g, "%2F");
+
+	const url = `${end_point}/w/rest.php/v1/page/${encodedTitle}/with_html`;
+
+	const data = await fetchJson(url);
+	return data?.html || "";
+}
+
+function get_endpoint_and_title(tr_type, title) {
+	let end_point = ENDPOINTS.medwikiToolforge;
+
+	if (tr_type === "all") {
+		// if title contains slashes
+		if (title.includes("/")) {
+			title = title + "/fulltext";
+		} else {
+			// end_point = ENDPOINTS.mdwiki_wmcloud;
+			end_point = ENDPOINTS.mdwiki;
+		}
+	}
+
+	title = end_point === ENDPOINTS.medwikiToolforge ? `Md:${title}` : title;
+
+	return { end_point, title };
+}
+
+function get_html_revision(HTMLText) {
+	if (HTMLText !== '') {
+		const matches = HTMLText.match(/Redirect\/revision\/(\d+)/);
+		// if (matches && matches[1]) {
+		if (matches?.[1]) {
+			const revision = matches[1];
+			return revision;
+		}
+	}
+	return "";
+}
+
+function removeUnlinkedWikibase(html) {
+	const dom = new DOMParser().parseFromString(html, "text/html");
+
+	const elements = dom.getElementsByTagName("span");
+
+	Array.from(elements).forEach(element => {
+		const lowerOuterHtml = element.outerHTML.toLowerCase();
+
+		if (lowerOuterHtml.includes("unlinkedwikibase") || lowerOuterHtml.includes("mdwiki revid")) {
+			// element.parentNode.removeChild(element);
+			// element.remove();
+
+			html = html.replace(element.outerHTML, '');
+		}
+	});
+
+	// return dom.documentElement.outerHTML;
+	return html;
+}
+
+async function get_from_medwiki_or_mdwiki_api(title, tr_type) {
+	const html = await getMedwikiHtml(title, tr_type);
+	if (!html) {
+		console.log("getMedwikiHtml: not found");
+		return false;
+	};
+
+	const cleanedHtml = removeUnlinkedWikibase(html);
+	const revision = get_html_revision(cleanedHtml);
+
+	const segmentedContent = await HtmltoSegments(cleanedHtml);
+	if (segmentedContent == "") {
+		console.log("HtmltoSegments: not found");
+		return false;
+	};
+	return {
+		sourceLanguage: "mdwiki",
+		title: title,
+		revision: revision,
+		segmentedContent: segmentedContent,
+		categories: []
+	};
+}
+
+async function get_new_html_2025(title, tr_type) {
+	const params = {
+		title: title
+	};
+	if (tr_type === "all") params.all = "all";
+	// ---
+	let host = (window.location.hostname === "mdwikicx.toolforge.org") ? window.location.hostname : "medwiki.toolforge.org";
+	// ---
+	const url = `https://${host}/new_html/index.php?${$.param(params)}`;
+	// ---
+	return await fetchJson(url);
+}
+
+async function get_Segments_from_mdwiki(targetLanguage, title, tr_type) {
+	// var url = "https://medwiki.toolforge.org/get_html/index.php";
+
+	let host = (window.location.hostname === "mdwikicx.toolforge.org") ? window.location.hostname : "medwiki.toolforge.org";
+
+	const params = {
+		sourcelanguage: "mdwiki",
+		targetlanguage: targetLanguage,
+		tr_type: tr_type,
+		title: title
+	};
+	// ---
+	if (tr_type !== "all") params.section0 = 1;
+	if (tr_type === "all") params.all = "all";
+	// ---
+	const url = `https://${host}/get_html/index.php?${$.param(params)}`;
+
+	const result = await fetchJson(url);
+	return result;
+}
+
+async function fetchSourcePageContent_mdwiki_user_test(page_title, targetLanguage, tr_type, user_name) {
+	mdwiki_last_url.url = "";
+	// ---
+	// make first litter Capital
+	page_title = page_title.charAt(0).toUpperCase() + page_title.slice(1);
+	// ---
+	// if page_title start with "Video:" then tr_type = all
+	if (page_title.startsWith("Video:") || page_title.startsWith("video:")) {
+		tr_type = "all";
+	}
+	// ---
+	// Manual normalisation to avoid redirects on spaces but not to break namespaces
+	const title = page_title.replace(/ /g, "_");
+	// ---
+	console.log("tr_type: ", tr_type)
+	// ---
+	let result1;
+	// ---
+	if (user_name === "get_mdtexts_2024") {
+		result1 = await get_mdtexts_2024(title);
+	} else if (user_name === "get_from_medwiki_or_mdwiki_api") {
+		result1 = await get_from_medwiki_or_mdwiki_api(title, tr_type);
+	} else if (user_name === "get_new_html_2025") {
+		result1 = await get_new_html_2025(title, tr_type);
+	} else if (user_name === "get_Segments_from_mdwiki") {
+		result1 = await get_Segments_from_mdwiki(targetLanguage, title, tr_type);
+	}
+	// ---
+	return result1;
+}
+
+async function fetchSourcePageContent_mdwiki_new(page_title, targetLanguage, tr_type, user_name) {
+	// make first litter Capital
+	page_title = page_title.charAt(0).toUpperCase() + page_title.slice(1);
+	// ---
+	// Manual normalisation to avoid redirects on spaces but not to break namespaces
+	const title = page_title.replace(/ /g, "_");
+	// ---
+	// Try 2025 method first if applicable
+	// if (shouldUse2025()) {
+	const result0 = await get_new_html_2025(title, tr_type);
+	if (result0) return result0;
+	// }
+	// ---
+	// try segments method
+	// ---
+	const result = await get_Segments_from_mdwiki(targetLanguage, title, tr_type);
+	if (result) return result;
+	// ---
+	// const result2 = await get_mdtexts_2024(title);
+	// if (result2) return result2;
+	// ---
+	// Try medwiki method if on medwiki host
+	// if (isMedwikiHost()) {
+	const result1 = await get_from_medwiki_or_mdwiki_api(title, tr_type);
+	if (result1) return result1;
+	// }
+	// ---
+	return "";
+}
+
+async function fetchSourcePageContent_mdwiki(page_title, targetLanguage, tr_type, user_name) {
+	mdwiki_last_url.url = "";
+	// ---
+	// if page_title start with "Video:" then tr_type = all
+	if (page_title.startsWith("Video:") || page_title.startsWith("video:")) {
+		tr_type = "all";
+	}
+	// ---
+	const result = await fetchSourcePageContent_mdwiki_new(page_title, targetLanguage, tr_type, user_name);
+	// ---
+	// if (result && result.segmentedContent && targetLanguage === "sw") {
+	if (result?.segmentedContent && targetLanguage === "sw") {
+		result.categories = await add_sw_categories(result.segmentedContent);
+	}
+	// ---
+	return result;
+}
+
+mw.cx.TranslationMdwiki = {
+	fetchSourcePageContent_mdwiki,
+	fetchSourcePageContent_mdwiki_user_test,
+	get_last_url,
+}
diff --git a/modules/mw.cx.init.Translation.js b/modules/mw.cx.init.Translation.js
index 3cdaa79..26cbae0 100644
--- a/modules/mw.cx.init.Translation.js
+++ b/modules/mw.cx.init.Translation.js
@@ -85,6 +85,18 @@ mw.cx.init.Translation.prototype.init = function () {
 
 			this.sourceWikiPage.setRevision(sourcePageContent.revision);
 
+			if (this.sourceWikiPage.getLanguage() == "mdwiki") {
+				var segmentedError = sourcePageContent.error;
+				if (segmentedError) {
+					this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), segmentedError);
+					// return;
+				} else if (sourcePageContent.segmentedContent.length === 0) {
+					// if length of segmentedContent is 0, it means the source page is empty print error message
+					this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), 'Source text is empty refresh the page.');
+					// return;
+				}
+			}
+
 			return this.initTranslationModel(sourcePageContent.segmentedContent, draft).then((translationModel) => {
 				this.translationModel = translationModel;
 
@@ -258,7 +270,16 @@ mw.cx.init.Translation.prototype.attachToDOM = function (veTarget) {
  * @param {mw.cx.SiteMapper} siteMapper
  * @return {Promise}
  */
+
 mw.cx.init.Translation.prototype.fetchSourcePageContent = function (wikiPage, targetLanguage, siteMapper) {
+
+	if (wikiPage.getLanguage() === "mdwiki") {
+		const pageTitle = wikiPage.getTitle();
+		const userName = mw.user.getName();
+		const translationType = this.config.tr_type;
+		return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(pageTitle, targetLanguage, translationType, userName);
+	}
+
 	const fetchParams = {
 		$sourcelanguage: siteMapper.getWikiDomainCode(wikiPage.getLanguage()),
 		$targetlanguage: targetLanguage,
@@ -372,6 +393,12 @@ mw.cx.init.Translation.prototype.fetchDraftTranslation = function (
  * @return {Promise<mw.cx.dm.DraftTranslationDTO|null>} Draft or null.
  */
 mw.cx.init.Translation.prototype.fetchDraftTranslationSuccess = function (draft, conflict) {
+	const admin_users = ['Doc James', 'Mr. Ibrahem 1'];
+	if (admin_users.includes(mw.user.getName())) {
+		console.log('[CX] fetchDraftTranslationSuccess. admin_users');
+		// Stop further processing
+		return Promise.resolve(null);
+	}
 	// Do not allow two users to start a draft at the same time. The API only returns
 	// a conflict (providing the conflicting translator's name and gender, if this is the case.
 	if (conflict) {
diff --git a/modules/mw.cx.init.js b/modules/mw.cx.init.js
index 7421164..58432a2 100644
--- a/modules/mw.cx.init.js
+++ b/modules/mw.cx.init.js
@@ -59,10 +59,11 @@
 		services = {
 			siteMapper: mw.cx.siteMapper
 		};
-
 		services.requestManager = new mw.cx.MwApiRequestManager( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.siteMapper );
 		services.MTService = new mw.cx.MachineTranslationService( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.siteMapper );
 		services.MTManager = new mw.cx.MachineTranslationManager( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.MTService );
+		services.campaign = query.campaign;
+		services.tr_type = query.tr_type;
 
 		getTargetTitle( query.targettitle, sourceTitle, services.MTService ).then( function ( targetTitle ) {
 			const sourceWikiPage = new mw.cx.dm.WikiPage( sourceTitle, mw.cx.sourceLanguage, sourceRevision, sourceSectionTitle );
diff --git a/modules/source/mw.cx.SelectedSourcePage.js b/modules/source/mw.cx.SelectedSourcePage.js
index a15f7f8..47cfb0f 100644
--- a/modules/source/mw.cx.SelectedSourcePage.js
+++ b/modules/source/mw.cx.SelectedSourcePage.js
@@ -364,9 +364,12 @@ mw.cx.SelectedSourcePage.prototype.setData = function (pageTitle, href, config)
 	this.getPageInfo(pageTitle, params).done(function (data) {
 		this.renderPageViews(data.pageviews);
 
-		const numOfLanguages =
+		var numOfLanguages =
 			config.numOfLanguages ||
 			(OO.getProp(data, 'langlinkscount') || 0) + 1;
+		if (data.langlinks) {
+			numOfLanguages = data.langlinks.length + 1;
+		}
 		this.languageCount.setLabel(mw.language.convertNumber(numOfLanguages));
 
 		// Reset source page titles
@@ -385,14 +388,21 @@ mw.cx.SelectedSourcePage.prototype.setData = function (pageTitle, href, config)
 
 		const languagesPageExistsIn = Object.keys(this.sourcePageTitles);
 		const languageDecorator = function ($language, languageCode) {
-			if (languagesPageExistsIn.indexOf(languageCode) < 0) {
+			// hide en, simple code
+			if (languageCode === 'en' || languageCode === 'simple') {
+				$language.css('display', 'none');
+			} else if (languagesPageExistsIn.indexOf(languageCode) < 0) {
 				$language.css('font-weight', 'bold');
+			} else {
+				// hide already in languages
+				// $language.css( 'display', 'none' );
 			}
 		};
 
-		this.languageFilter.fillSourceLanguages(languagesPageExistsIn, true, {
-			ulsPurpose: 'cx-selectedpage-source'
-		});
+		// disable this, show only mdwiki
+		// this.languageFilter.fillSourceLanguages( languagesPageExistsIn, true, {
+		// 	ulsPurpose: 'cx-selectedpage-source'
+		// } );
 		this.languageFilter.fillTargetLanguages(null, true, {
 			ulsPurpose: 'cx-selectedpage-target',
 			languageDecorator: languageDecorator
diff --git a/modules/ui/mw.cx.ui.ArticleColumn.js b/modules/ui/mw.cx.ui.ArticleColumn.js
index 9da9f08..f228247 100644
--- a/modules/ui/mw.cx.ui.ArticleColumn.js
+++ b/modules/ui/mw.cx.ui.ArticleColumn.js
@@ -45,7 +45,7 @@ mw.cx.ui.ArticleColumn = function ( config ) {
 	} else {
 		this.titleWidget = new mw.cx.ui.PageTitleWidget(
 			new mw.cx.dm.PageTitleModel(),
-			{ value: config.title }
+			{ value: config.title, language: config.language }
 		);
 	}
 	this.direction = $.uls.data.getDir( config.language );
diff --git a/modules/ui/widgets/mw.cx.ui.PageTitleWidget.js b/modules/ui/widgets/mw.cx.ui.PageTitleWidget.js
index 13d4ed9..4c2cd56 100644
--- a/modules/ui/widgets/mw.cx.ui.PageTitleWidget.js
+++ b/modules/ui/widgets/mw.cx.ui.PageTitleWidget.js
@@ -92,12 +92,12 @@ mw.cx.ui.PageTitleWidget.prototype.validateTitle = function ( value ) {
 
 mw.cx.ui.PageTitleWidget.prototype.getExistingTitleWarning = function () {
 	var href = mw.util.getUrl( this.getValue() );
-	var href2 = "//" + this.language + ".wikipedia.org/wiki/" + encodeURIComponent( this.getValue() );
+	var wikipediaUrl = "//" + this.language + ".wikipedia.org/wiki/" + encodeURIComponent( this.getValue() );
 	return {
 		name: 'existing-title',
 		message: mw.message(
 			'cx-tools-linter-page-exists-message',
-			$( '<a>' ).prop( 'href', href2 ).text( this.getValue() )
+			$( '<a>' ).prop( 'href', wikipediaUrl ).text( this.getValue() )
 		),
 		messageInfo: {
 			title: mw.msg( 'cx-tools-linter-page-exists' ),
diff --git a/modules/ve-cx/init/ve.init.mw.CXTarget.js b/modules/ve-cx/init/ve.init.mw.CXTarget.js
index e879813..7077db3 100644
--- a/modules/ve-cx/init/ve.init.mw.CXTarget.js
+++ b/modules/ve-cx/init/ve.init.mw.CXTarget.js
@@ -535,11 +535,11 @@ ve.init.mw.CXTarget.prototype.onSurfaceReady = function () {
 		// By: Ibrahem Qasim
 		const sections = $('.cx-column--translation article').find('section');
 
-		for (let i = 0; i < sections.length; i++) {
-			setTimeout(() => {
-				this.prefetchTranslationForSection(i, true);
-			}, i * 1000);
-		}
+		for ( let i = 0; i < sections.length; i++ ) {
+			setTimeout( () => {
+				this.prefetchTranslationForSection( i, true );
+			}, i * 1000 );
+
 	}
 };
 
@@ -1120,19 +1120,17 @@ ve.init.mw.CXTarget.prototype.changeContentSource = function (
  *
  * @param {number} sectionNumber
  */
-ve.init.mw.CXTarget.prototype.prefetchTranslationForSection = function ( sectionNumber, set_it ) {
+ve.init.mw.CXTarget.prototype.prefetchTranslationForSection = function ( sectionNumber, applyImmediately ) {
 	const $section = this.sourceSurface.$element.find( '#cxSourceSection' + sectionNumber );
 	if ( $section.length ) {
 		this.MTManager.getPreferredProvider().then( function ( provider ) {
 			this.translateSection( $section.prop( 'id' ), provider );
 			// auto translation
-			if (set_it) {
+         	if ( applyImmediately ) {
 				const sectionNode = this.getTargetSectionNode( $section.prop( 'id' ) );
-				this.changeContentSource(
-					sectionNode,
-					null,
-					provider
-				);
+				if ( sectionNode ) {
+					this.changeContentSource( sectionNode, null, provider );
+				}
 			}
 		}.bind( this ) );
 	}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 15**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>modules/mw.cx.init.js (1)</summary><blockquote>

`74-76`: **Consider consistent campaign handling**

The campaign parameter is duplicated - it's both added to the `services` object (line 65) and used directly from `query.campaign` here. For consistency and maintainability, consider using the value from the services object.


```diff
-if ( query.campaign ) {
+if ( services.campaign ) {
-	mw.hook( 'mw.cx.cta.accept' ).fire( query.campaign, mw.cx.sourceLanguage, sourceTitle, mw.cx.targetLanguage );
+	mw.hook( 'mw.cx.cta.accept' ).fire( services.campaign, mw.cx.sourceLanguage, sourceTitle, mw.cx.targetLanguage );
}
```

</blockquote></details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js (2)</summary><blockquote>

`367-373`: **Prefer `const` and de-duplicate the language-count logic**

1. `var` can be replaced with `const`; the variable is never reassigned.  
2. The fallback logic (`langlinkscount`) is now dead code because the `if (data.langlinks)` branch always overwrites `numOfLanguages`.  
   Consider computing the value once to keep the intent clear.

```diff
-		var numOfLanguages =
-			config.numOfLanguages ||
-			(OO.getProp(data, 'langlinkscount') || 0) + 1;
-		if (data.langlinks) {
-			numOfLanguages = data.langlinks.length + 1;
-		}
+		const numOfLanguages = config.numOfLanguages ??
+			((data.langlinks ? data.langlinks.length : (OO.getProp(data, 'langlinkscount') || 0)) + 1);
```

This removes the redundant overwrite and aligns with modern JS best practices.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 367-369: Unexpected var, use let or const instead.

(no-var)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 370-370: There must be a space after this paren.

(space-in-parens)

---

[error] 370-370: There must be a space before this paren.

(space-in-parens)

---

[error] 373-373: There must be a space after this paren.

(space-in-parens)

---

[error] 373-373: There must be a space after this paren.

(space-in-parens)

---

[error] 373-373: There must be a space before this paren.

(space-in-parens)

---

[error] 373-373: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`390-398`: **UI rule-of-thumb: avoid hard-coding language exclusions inside decorators**

Hiding `'en'` and `'simple'` directly in `languageDecorator` couples UI behaviour to specific language codes and may surprise future maintainers (or break A-B tests).

If these languages must be suppressed, consider passing a `hiddenLanguages` array via options or moving the logic to CSS/feature flag so that it is discoverable and configurable.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 390-390: There must be a space after this paren.

(space-in-parens)

---

[error] 390-390: There must be a space before this paren.

(space-in-parens)

---

[error] 392-392: There must be a space after this paren.

(space-in-parens)

---

[error] 392-392: There must be a space before this paren.

(space-in-parens)

---

[error] 393-393: There must be a space after this paren.

(space-in-parens)

---

[error] 393-393: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 395-395: There must be a space after this paren.

(space-in-parens)

---

[error] 395-395: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>app/src/wiki/mw/api/page.js (1)</summary><blockquote>

`240-248`: **Delegate decision logic to a helper to avoid `if (sourceLanguage === 'mdwiki')` scattering**

`fetchSegmentedContent` now forks for every mdwiki call.  
Extracting the mapping into a lookup table (e.g. `customSegmentFetchers`) keeps this function short and prevents future duplication when other special-case wikis are added.

```js
const customSegmentFetchers = {
  mdwiki: fetchSegmentedContent_mdwiki,
  // future sources…
};

const fetcher = customSegmentFetchers[sourceLanguage];
return fetcher
  ? fetcher(sourceLanguage, targetLanguage, sourceTitle, revision)
  : defaultFetcher();
```

This is a non-blocking suggestion but will improve maintainability.

</blockquote></details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js (1)</summary><blockquote>

`191-235`: **Dead code: `fetchCXServerToken_issue()` is never invoked**

Keeping unused methods increases surface area and maintenance cost.  
If it is experimental, guard it behind a feature flag or remove it until needed.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 191-191: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 194-194: There must be a space after this paren.

(space-in-parens)

---

[error] 194-194: Strings must use singlequote.

(quotes)

---

[error] 194-194: There must be a space before this paren.

(space-in-parens)

---

[error] 195-199: Unexpected var, use let or const instead.

(no-var)

---

[error] 198-198: Strings must use singlequote.

(quotes)

---

[error] 198-198: Unexpected trailing comma.

(comma-dangle)

---

[error] 199-200: Missing semicolon.

(semi)

---

[error] 203-204: Missing semicolon.

(semi)

---

[error] 205-205: Unexpected var, use let or const instead.

(no-var)

---

[error] 205-205: Strings must use singlequote.

(quotes)

---

[error] 205-205: There must be a space after this paren.

(space-in-parens)

---

[error] 205-205: There must be a space before this paren.

(space-in-parens)

---

[error] 205-206: Missing semicolon.

(semi)

---

[error] 207-207: There must be a space after this paren.

(space-in-parens)

---

[error] 207-207: There must be a space before this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: Unexpected console statement.

(no-console)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 214-214: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: Strings must use singlequote.

(quotes)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: Unexpected console statement.

(no-console)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 223-223: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 225-225: Unexpected console statement.

(no-console)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: There must be a space before this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 226-227: Missing semicolon.

(semi)

---

[error] 230-230: There must be a space after this paren.

(space-in-parens)

---

[error] 233-233: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.TargetArticle.js (3)</summary><blockquote>

`18-21`: **Remove noisy console logging**

`console.log` ships to all production browsers and leaks internals to users.

```diff
- console.log('MWCXTargetArticle: campaign: ' + this.campaign);
```
Replace with `mw.log( … )` or drop entirely.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`217-225`: **Debug logging left in success path**

Multiple `console.log` calls remain. These add latency (JSON stringify) and break CSP reports.

Please wrap them in `if ( mw.config.get( 'debug' ) )` blocks or remove.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 217-217: Unexpected console statement.

(no-console)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: Strings must use singlequote.

(quotes)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: Strings must use singlequote.

(quotes)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: Strings must use singlequote.

(quotes)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: Unexpected console statement.

(no-console)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`248-251`: **Call instance method instead of reaching via prototype**

```diff
- mw.cx.TargetArticle.prototype.addMdwikiLinks( … );
+ this.addMdwikiLinks( … );
```

Direct prototype access ignores subclasses and makes tree-shaking harder.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 248-248: There must be a space after this paren.

(space-in-parens)

---

[error] 248-248: Strings must use singlequote.

(quotes)

---

[error] 248-248: There must be a space before this paren.

(space-in-parens)

---

[error] 249-249: Unexpected var, use let or const instead.

(no-var)

---

[error] 249-249: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0d7b080b1ff051dcfd6869d5f5e6487f02794de1 and a3c4be9fb8029eb3ccef1c257e17a2b7b00e0d13.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `app/src/wiki/mw/api/page.js` (2 hunks)
* `includes/ActionApi/ApiContentTranslationPublish.php` (7 hunks)
* `modules/mw.cx.MachineTranslationManager.js` (1 hunks)
* `modules/mw.cx.MachineTranslationService.js` (4 hunks)
* `modules/mw.cx.MwApiRequestManager.js` (1 hunks)
* `modules/mw.cx.TargetArticle.js` (6 hunks)
* `modules/mw.cx.TranslationController.js` (2 hunks)
* `modules/mw.cx.init.Translation.js` (3 hunks)
* `modules/mw.cx.init.js` (1 hunks)
* `modules/source/mw.cx.SelectedSourcePage.js` (2 hunks)
* `modules/ui/mw.cx.ui.ArticleColumn.js` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>modules/mw.cx.TargetArticle.js (2)</summary><blockquote>

<details>
<summary>modules/base/mw.cx.SiteMapper.js (2)</summary>

* `config` (19-19)
* `url` (147-147)

</details>
<details>
<summary>modules/ve-cx/init/ve.init.mw.CXTarget.js (1)</summary>

* `html` (1095-1095)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>modules/mw.cx.init.js</summary>

[error] 66-66: Identifier 'tr_type' is not in camel case.

(camelcase)

</details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js</summary>

[error] 367-369: Unexpected var, use let or const instead.

(no-var)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 370-370: There must be a space after this paren.

(space-in-parens)

---

[error] 370-370: There must be a space before this paren.

(space-in-parens)

---

[error] 392-392: There must be a space after this paren.

(space-in-parens)

---

[error] 392-392: There must be a space before this paren.

(space-in-parens)

---

[error] 393-393: There must be a space after this paren.

(space-in-parens)

---

[error] 393-393: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 395-395: There must be a space after this paren.

(space-in-parens)

---

[error] 395-395: There must be a space before this paren.

(space-in-parens)

---

[error] 404-404: Unexpected tab character.

(no-tabs)

</details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js</summary>

[error] 156-156: There must be a space after this paren.

(space-in-parens)

---

[error] 156-156: Strings must use singlequote.

(quotes)

---

[error] 156-156: There must be a space before this paren.

(space-in-parens)

---

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: A space is required after '['.

(array-bracket-spacing)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: A space is required before ']'.

(array-bracket-spacing)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

---

[error] 170-170: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 173-173: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 173-173: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 173-173: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 178-178: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: Strings must use singlequote.

(quotes)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 187-187: There must be a space before this paren.

(space-in-parens)

---

[error] 187-188: Missing semicolon.

(semi)

---

[error] 189-190: Missing semicolon.

(semi)

---

[error] 191-191: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 194-194: There must be a space after this paren.

(space-in-parens)

---

[error] 194-194: Strings must use singlequote.

(quotes)

---

[error] 194-194: There must be a space before this paren.

(space-in-parens)

---

[error] 195-199: Unexpected var, use let or const instead.

(no-var)

---

[error] 198-198: Strings must use singlequote.

(quotes)

---

[error] 198-198: Unexpected trailing comma.

(comma-dangle)

---

[error] 199-200: Missing semicolon.

(semi)

---

[error] 203-204: Missing semicolon.

(semi)

---

[error] 205-205: Unexpected var, use let or const instead.

(no-var)

---

[error] 205-205: Strings must use singlequote.

(quotes)

---

[error] 205-205: There must be a space after this paren.

(space-in-parens)

---

[error] 205-205: There must be a space before this paren.

(space-in-parens)

---

[error] 205-206: Missing semicolon.

(semi)

---

[error] 207-207: There must be a space after this paren.

(space-in-parens)

---

[error] 207-207: There must be a space before this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: Unexpected console statement.

(no-console)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 214-214: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: Strings must use singlequote.

(quotes)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: Unexpected console statement.

(no-console)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 223-223: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 225-225: Unexpected console statement.

(no-console)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: There must be a space before this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 226-227: Missing semicolon.

(semi)

---

[error] 230-230: There must be a space after this paren.

(space-in-parens)

---

[error] 233-233: There must be a space before this paren.

(space-in-parens)

---

[error] 239-239: There must be a space after this paren.

(space-in-parens)

---

[error] 239-239: Strings must use singlequote.

(quotes)

---

[error] 239-239: There must be a space before this paren.

(space-in-parens)

---

[error] 240-244: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Strings must use singlequote.

(quotes)

---

[error] 243-243: Unexpected trailing comma.

(comma-dangle)

---

[error] 244-245: Missing semicolon.

(semi)

---

[error] 248-249: Missing semicolon.

(semi)

---

[error] 250-250: Unexpected var, use let or const instead.

(no-var)

---

[error] 250-250: Strings must use singlequote.

(quotes)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 252-252: There must be a space after this paren.

(space-in-parens)

---

[error] 252-252: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 255-255: Unexpected console statement.

(no-console)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: There must be a space before this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 334-335: Too many blank lines at the end of file. Max of 0 allowed.

(no-multiple-empty-lines)

</details>
<details>
<summary>modules/mw.cx.init.Translation.js</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 89-89: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 274-274: There must be a space after this paren.

(space-in-parens)

---

[error] 274-274: There must be a space before this paren.

(space-in-parens)

---

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 277-277: Identifier 'page_title' is not in camel case.

(camelcase)

---

[error] 277-277: 'page_title' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 278-278: Identifier 'user_name' is not in camel case.

(camelcase)

---

[error] 278-278: 'user_name' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 279-279: Identifier 'tr_type' is not in camel case.

(camelcase)

---

[error] 279-279: 'tr_type' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

---

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>
<details>
<summary>modules/mw.cx.TargetArticle.js</summary>

[error] 13-13: There must be a space after this paren.

(space-in-parens)

---

[error] 13-13: There must be a space before this paren.

(space-in-parens)

---

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

---

[error] 217-217: Unexpected console statement.

(no-console)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: Strings must use singlequote.

(quotes)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: Strings must use singlequote.

(quotes)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: Strings must use singlequote.

(quotes)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: Unexpected console statement.

(no-console)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 227-227: There must be a space after this paren.

(space-in-parens)

---

[error] 227-227: There must be a space before this paren.

(space-in-parens)

---

[error] 228-228: Unexpected var, use let or const instead.

(no-var)

---

[error] 229-229: There must be a space after this paren.

(space-in-parens)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: There must be a space before this paren.

(space-in-parens)

---

[error] 232-232: Unexpected var, use let or const instead.

(no-var)

---

[error] 232-232: Strings must use singlequote.

(quotes)

---

[error] 233-233: Unexpected var, use let or const instead.

(no-var)

---

[error] 233-233: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 233-233: Strings must use singlequote.

(quotes)

---

[error] 234-234: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: Unexpected console statement.

(no-console)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 238-238: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

---

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

---

[error] 248-248: There must be a space after this paren.

(space-in-parens)

---

[error] 248-248: Strings must use singlequote.

(quotes)

---

[error] 248-248: There must be a space before this paren.

(space-in-parens)

---

[error] 249-249: Unexpected var, use let or const instead.

(no-var)

---

[error] 249-249: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 256-256: There must be a space after this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 308-308: 'mddx' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 308-308: Strings must use singlequote.

(quotes)

---

[error] 310-310: 'mddxlink' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 310-310: 'mddxlink' is assigned a value but never used.

(no-unused-vars)

---

[error] 312-312: There must be a space after this paren.

(space-in-parens)

---

[error] 312-312: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 314-314: There must be a space after this paren.

(space-in-parens)

---

[error] 314-314: Strings must use singlequote.

(quotes)

---

[error] 314-314: There must be a space before this paren.

(space-in-parens)

---

[error] 316-316: There must be a space after this paren.

(space-in-parens)

---

[error] 316-316: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 320-320: There must be a space after this paren.

(space-in-parens)

---

[error] 320-320: There must be a space before this paren.

(space-in-parens)

---

[error] 321-321: There must be a space after this paren.

(space-in-parens)

---

[error] 321-321: There must be a space before this paren.

(space-in-parens)

---

[error] 322-322: There must be a space after this paren.

(space-in-parens)

---

[error] 322-322: There must be a space before this paren.

(space-in-parens)

---

[error] 323-323: There must be a space after this paren.

(space-in-parens)

---

[error] 323-323: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: Strings must use singlequote.

(quotes)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 435-435: 'wikiurl' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 436-436: There must be a space after this paren.

(space-in-parens)

---

[error] 436-436: There must be a space before this paren.

(space-in-parens)

---

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>modules/ui/mw.cx.ui.ArticleColumn.js (1)</summary>

`48-48`: **Appropriate enhancement for language context**

Good addition of the language parameter to the PageTitleWidget initialization. This ensures that language-specific behaviors are properly handled, which is especially important for the "mdwiki" source language support mentioned in the PR objectives.

</details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js (1)</summary>

`402-406`: **Commented-out `fillSourceLanguages` may break the selector in non-mdwiki flows**

Disabling the call means the source-language ULS will never be populated after a page is selected.  
If this behaviour is intended only for `mdwiki`, add an explicit guard rather than an in-code comment:

```js
if (config.sourceLanguage === 'mdwiki') {
    // custom workflow – do not refill source languages
} else {
    this.languageFilter.fillSourceLanguages( languagesPageExistsIn, true, { ulsPurpose: 'cx-selectedpage-source' } );
}
```

Explicit branching prevents accidental regression for other wikis.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 404-404: Unexpected tab character.

(no-tabs)

---

[error] 406-406: There must be a space after this paren.

(space-in-parens)

</details>

</details>

</details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js (1)</summary>

`154-158`: **Hard-coding `"Google"` for target language `"or"` bypasses CX-server configuration**

Returning a static provider list means:

* Any CX-server additions/removals are ignored.
* The UI may advertise Google even when quota is exhausted or disabled.

Consider querying CX-server first and falling back to `["Google"]` only when the response is empty or returns a 4xx/5xx error.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 156-156: There must be a space after this paren.

(space-in-parens)

---

[error] 156-156: Strings must use singlequote.

(quotes)

---

[error] 156-156: There must be a space before this paren.

(space-in-parens)

---

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: A space is required after '['.

(array-bracket-spacing)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: A space is required before ']'.

(array-bracket-spacing)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-10T02:03:34Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (14)</summary><blockquote>

<details>
<summary>modules/mw.cx.MwApiRequestManager.js (1)</summary><blockquote>

`25-25`: **Uncomment or document the mdwiki handling logic**

This commented code is part of the special handling for "mdwiki" source language mentioned in the PR objectives. For consistency, either uncomment this line and similar lines in other modules, or add a TODO comment explaining when and why this should be uncommented.

```diff
-// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
```

</blockquote></details>
<details>
<summary>modules/mw.cx.init.js (1)</summary><blockquote>

`65-66`: **Fix variable naming convention and add validation**

The variable `tr_type` violates JavaScript's camelCase naming convention. Additionally, neither of these variables have validation or default values.

```diff
-services.campaign = query.campaign;
-services.tr_type = query.tr_type;
+// Add campaign information for tracking
+services.campaign = query.campaign || null;
+// Translation type parameter (camelCase naming convention)
+services.trType = query.tr_type || null;
```

While the campaign parameter is used later in the code (lines 74-76), there's no usage of `tr_type` in this file. Ensure this new property is actually utilized somewhere.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 66-66: Identifier 'tr_type' is not in camel case.

(camelcase)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.MachineTranslationManager.js (1)</summary><blockquote>

`25-25`: **Ensure consistent mdwiki→en mapping in MachineTranslationManager.js**

The codebase applies special handling for "mdwiki" in several places, but this mapping is currently commented out. This will cause translation requests for mdwiki sources to be sent as "mdwiki" (and likely rejected) rather than "en."

```diff
-// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
```

After uncommenting, consider adding or updating tests to verify that when sourceLanguage==="mdwiki" it is properly remapped to "en" before making the MT request.

</blockquote></details>
<details>
<summary>modules/mw.cx.TranslationController.js (1)</summary><blockquote>

`14-18`: _⚠️ Potential issue_

**Guard `config` parameter – avoid runtime `TypeError` when omitted**

`config` is optional in callers but it is dereferenced unconditionally later (`this.config.campaign` on line 46).  
If a caller passes nothing, the constructor still blows up.

```diff
-mw.cx.TranslationController = function MwCxTranslationController(
-	translation, veTarget, siteMapper, config
-) {
-	this.config = config;
+mw.cx.TranslationController = function MwCxTranslationController(
+	translation, veTarget, siteMapper, config = {}
+) {
+	this.config = config;              // always an object
```

This one-liner fixes every subsequent access without touching call-sites.

</blockquote></details>
<details>
<summary>app/src/wiki/mw/api/page.js (1)</summary><blockquote>

`191-229`: _⚠️ Potential issue_

**`fetchSegmentedContent_mdwiki` – network & data-validation gaps**

1. `fetch` ignores the jQuery-style `dataType: 'json'` option – remove it.  
2. Missing `response.ok` check ⇒ 4xx/5xx give uncaught `SyntaxError`.  
3. No validation that `segmentedContent` exists.  
4. `targetWikiCode` is computed but never used.  
5. `var` + string concatenation – prefer `const` & template literals.

```diff
-  const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
…
-  const options = {
-    method: 'GET',
-    dataType: 'json'
-  }
+  /* targetWikiCode currently unused.  Remove or utilise. */
+
+  const options = { method: 'GET' };
…
-  const result = fetch(cxServerApiURL, options)
-    .then((response) => response.json())
-    .then((response) => response.segmentedContent);
+  const result = fetch(cxServerApiURL, options)
+    .then((res) => {
+      if (!res.ok) {
+        throw new Error(`mdwiki fetch failed (${res.status})`);
+      }
+      return res.json();
+    })
+    .then((json) => {
+      if (!json.segmentedContent) {
+        throw new Error('mdwiki: missing segmentedContent in response');
+      }
+      return json.segmentedContent;
+    });
```

Failing fast keeps callers predictable and surfaces real problems instead of cryptic promise rejections.

</blockquote></details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js (1)</summary><blockquote>

`239-259`: _⚠️ Potential issue_

**`fetchCXServerToken` branch for “mdwiki” returns an invalid token object**

`getCXServerToken()` assumes the resolved object has numeric `age` and string `jwt`.  
The current implementation:

```
fetch(...).then(res => res.json()) // no ok check
```

• No `response.ok` handling  
• No validation of `age` / `jwt` fields  
• Network failure resolves to `undefined`, leading to `token.age` → `NaN` and a subsequent crash.

```diff
-const result = fetch(url, options)
-  .then((response) => response.json())
-  .catch(...);
-return result;
+return fetch(url, { method: 'GET' })
+  .then((res) => {
+    if (!res.ok) {
+      throw new Error(`mdwiki token fetch failed (${res.status})`);
+    }
+    return res.json();
+  })
+  .then((token) => {
+    if (!token || typeof token.age !== 'number' || !token.jwt) {
+      throw new Error('Invalid token format from mdwiki');
+    }
+    return token;
+  });
```

Fail fast with descriptive errors and satisfy the downstream contract.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 239-239: There must be a space after this paren.

(space-in-parens)

---

[error] 239-239: Strings must use singlequote.

(quotes)

---

[error] 239-239: There must be a space before this paren.

(space-in-parens)

---

[error] 240-244: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Strings must use singlequote.

(quotes)

---

[error] 243-243: Unexpected trailing comma.

(comma-dangle)

---

[error] 244-245: Missing semicolon.

(semi)

---

[error] 248-249: Missing semicolon.

(semi)

---

[error] 250-250: Unexpected var, use let or const instead.

(no-var)

---

[error] 250-250: Strings must use singlequote.

(quotes)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 252-252: There must be a space after this paren.

(space-in-parens)

---

[error] 252-252: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 255-255: Unexpected console statement.

(no-console)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: There must be a space before this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.init.Translation.js (3)</summary><blockquote>

`88-98`: **Still falls-through on fatal errors + ESLint violations remain unchanged**  

The block re-introduces the same issues called out in the previous review: loose `==`, `var`, snake_case, missing early `return`, and untranslated UI messages. Because no changes were made, the earlier advice (replace with strict comparison, use `const`, early-exit with `Promise.reject`, camelCase names, single quotes) is still applicable.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 89-89: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`274-281`: **PII leak & non-idiomatic JS – please refactor**  

`mw.user.getName()` is forwarded to a 3rd-party endpoint, exposing personally-identifiable information. Replace it with an opaque identifier or omit entirely unless the user has given explicit consent.  
In addition, prefer `const`, camelCase and single quotes:

```diff
-if ( wikiPage.getLanguage() === "mdwiki" ) {
-    let page_title = wikiPage.getTitle();
-    let user_name  = mw.user.getName();
-    let tr_type    = this.config.tr_type;
-    return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
-        page_title, targetLanguage, tr_type, user_name );
+if ( wikiPage.getLanguage() === 'mdwiki' ) {
+    const pageTitle       = wikiPage.getTitle();
+    const translationType = this.config.tr_type;
+    const userToken       = null; // ⚠️ do not forward real user name
+
+    return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
+        pageTitle, targetLanguage, translationType, userToken );
 }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 274-274: There must be a space after this paren.

(space-in-parens)

---

[error] 274-274: There must be a space before this paren.

(space-in-parens)

---

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 277-277: Identifier 'page_title' is not in camel case.

(camelcase)

---

[error] 277-277: 'page_title' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 278-278: Identifier 'user_name' is not in camel case.

(camelcase)

---

[error] 278-278: 'user_name' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 279-279: Identifier 'tr_type' is not in camel case.

(camelcase)

---

[error] 279-279: 'tr_type' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`396-401`: **Hard-coded admin white-list persists**  

Embedding real user names in source code is brittle and bypasses MediaWiki’s permission system.  
Reuse `mw.user.isAllowed( 'contenttranslation-admin' )` or a custom right instead.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.TargetArticle.js (2)</summary><blockquote>

`241-247`: **Null-safety missing for `mdwiki_result` title**  

`publishResult.save_result_all` or the nested `mdwiki_result.edit.title` may be undefined, causing a runtime error.

```diff
- var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+ const newTitle = OO.getProp(
+     publishResult, 'save_result_all', 'mdwiki_result', 'edit', 'title'
+ );
```

Guard access or use optional chaining when dropping IE11.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

---

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`687-704`: _⚠️ Potential issue_

**XSS risk – build DOM nodes, do not interpolate unsanitised title into HTML**  

`targetTitle` and `qid` are user-controlled and inserted via template literals, enabling malicious input like `<img onerror=alert(1)>`.

```diff
-var html = `<div style="float:left;">${link}${wdlink}</div>`;
-$('.cx-message-widget-details').append(html);
+const $box = $( '<div>' ).css( 'float', 'left' ).append( link );
+if ( wdlink ) {
+    $box.append( ' – ', wdlink );
+}
+$( '.cx-message-widget-details' ).append( $box );
```

Also switch `var`→`const`, `!=`→`!==`, camelCase (`wdResult`) and single quotes.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>includes/ActionApi/ApiContentTranslationPublish.php (3)</summary><blockquote>

`45-68`: **Global helper violates encapsulation & skips TLS verification**  

`post_to_target()` should be a private method of the class; defining it in the global namespace breaks autoloading and test isolation.  
Additionally, set `CURLOPT_SSL_VERIFYPEER` and `CURLOPT_SSL_VERIFYHOST` to avoid silent MITM attacks.

```diff
-private function post_to_target( array $params ) {
+private function postToTarget( array $params ) : array {
     …
-    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
+    curl_setopt_array( $ch, [
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_SSL_VERIFYPEER => true,
+        CURLOPT_SSL_VERIFYHOST => 2,
+    ] );
```

Rename the call sites accordingly.

---

`101-102`: **Dynamic property `$published_to` is deprecated on PHP 8.2**  

Declare it explicitly to avoid deprecation notices and future fatal errors:

```php
/** @var string */
private string $publishedTo = 'local';
```

Replace all usages of `$this->published_to` with `$this->publishedTo`.

---

`147-151`: **Class name typo – `Sitemapper` → `SiteMapper`**  

PHP class names are case-sensitive in namespaces; using the wrong case leads to fatal errors in production.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>modules/mw.cx.TranslationController.js (1)</summary><blockquote>

`44-48`: **`this.config.campaign` may be `undefined` – cascade to default value**

Even with the fix above, the campaign key itself can be missing.  Consider passing it defensively:

```diff
-	campaign: this.config.campaign
+	campaign: this.config.campaign || null
```

or decide on a meaningful default (empty string?).  
This prevents `undefined` from creeping into downstream API payloads.

</blockquote></details>
<details>
<summary>app/src/wiki/mw/api/page.js (1)</summary><blockquote>

`246-248`: **Case-sensitive language comparison**

`sourceLanguage === "mdwiki"` will fail for capitalised variants (`"MDWIKI"`).  
Prefer a case-insensitive test to be future-proof:

```diff
-if (sourceLanguage === "mdwiki") {
+if (sourceLanguage.toLowerCase() === "mdwiki") {
```

</blockquote></details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js (1)</summary><blockquote>

`367-374`: **Use `const`/`let` & simplify language-count logic**

`var` leaks to function scope and ESLint is already flagging it.  
Also you can compute `numOfLanguages` directly once.

```diff
-		var numOfLanguages =
-			config.numOfLanguages ||
-			(OO.getProp(data, 'langlinkscount') || 0) + 1;
-		if (data.langlinks) {
-			numOfLanguages = data.langlinks.length + 1;
-		}
+		const numOfLanguages = config.numOfLanguages ??
+			(data.langlinks ? data.langlinks.length + 1 :
+				(OO.getProp(data, 'langlinkscount') || 0) + 1);
```

Keeps scope tight and removes reassignment.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 367-369: Unexpected var, use let or const instead.

(no-var)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 370-370: There must be a space after this paren.

(space-in-parens)

---

[error] 370-370: There must be a space before this paren.

(space-in-parens)

---

[error] 373-373: There must be a space after this paren.

(space-in-parens)

---

[error] 373-373: There must be a space after this paren.

(space-in-parens)

---

[error] 373-373: There must be a space before this paren.

(space-in-parens)

---

[error] 373-373: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js (2)</summary><blockquote>

`191-229`: **Dead code – `fetchCXServerToken_issue` is never called**

Keeping unused branches increases maintenance burden and confuses future readers.  
Delete or integrate it; otherwise tools will flag it as dead.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 191-191: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 194-194: There must be a space after this paren.

(space-in-parens)

---

[error] 194-194: Strings must use singlequote.

(quotes)

---

[error] 194-194: There must be a space before this paren.

(space-in-parens)

---

[error] 195-199: Unexpected var, use let or const instead.

(no-var)

---

[error] 198-198: Strings must use singlequote.

(quotes)

---

[error] 198-198: Unexpected trailing comma.

(comma-dangle)

---

[error] 199-200: Missing semicolon.

(semi)

---

[error] 203-204: Missing semicolon.

(semi)

---

[error] 205-205: Unexpected var, use let or const instead.

(no-var)

---

[error] 205-205: Strings must use singlequote.

(quotes)

---

[error] 205-205: There must be a space after this paren.

(space-in-parens)

---

[error] 205-205: There must be a space before this paren.

(space-in-parens)

---

[error] 205-206: Missing semicolon.

(semi)

---

[error] 207-207: There must be a space after this paren.

(space-in-parens)

---

[error] 207-207: There must be a space before this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: Unexpected console statement.

(no-console)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 214-214: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: Strings must use singlequote.

(quotes)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: Unexpected console statement.

(no-console)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 223-223: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 225-225: Unexpected console statement.

(no-console)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: There must be a space before this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 226-227: Missing semicolon.

(semi)

</details>

</details>

---

`19-20`: **Commented-out language remap should be removed or made configurable**

Stray commented code (`// if (sourceLanguage === 'mdwiki') …`) clutters the file and invites re-enablement without review.

Remove or guard behind a feature flag.

</blockquote></details>
<details>
<summary>modules/mw.cx.TargetArticle.js (2)</summary><blockquote>

`18-21`: **Remove debug `console.log` before shipping**  

Console output clutters production logs and can leak runtime data in browsers. Wrap in `mw.log()` (respecting `$wgDebugLogGroups`) or remove completely.

```diff
- console.log('MWCXTargetArticle: campaign: ' + this.campaign);
+ // mw.log( 'MWCXTargetArticle: campaign: ' + this.campaign );
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`308-326`: **OAuth re-login banner: markup is safe, but prefer `OO.ui.MessageWidget`**  

Plain jQuery DOM manipulation works, yet CX already bundles OOUI. Building the banner via `new OO.ui.MessageWidget( { …, label: …, url: … } )` yields consistent styling and RTL support.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 308-308: 'mddx' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 308-308: Strings must use singlequote.

(quotes)

---

[error] 310-310: 'mddxlink' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 310-310: 'mddxlink' is assigned a value but never used.

(no-unused-vars)

---

[error] 312-312: There must be a space after this paren.

(space-in-parens)

---

[error] 312-312: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 314-314: There must be a space after this paren.

(space-in-parens)

---

[error] 314-314: Strings must use singlequote.

(quotes)

---

[error] 314-314: There must be a space before this paren.

(space-in-parens)

---

[error] 316-316: There must be a space after this paren.

(space-in-parens)

---

[error] 316-316: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 320-320: There must be a space after this paren.

(space-in-parens)

---

[error] 320-320: There must be a space before this paren.

(space-in-parens)

---

[error] 321-321: There must be a space after this paren.

(space-in-parens)

---

[error] 321-321: There must be a space before this paren.

(space-in-parens)

---

[error] 322-322: There must be a space after this paren.

(space-in-parens)

---

[error] 322-322: There must be a space before this paren.

(space-in-parens)

---

[error] 323-323: There must be a space after this paren.

(space-in-parens)

---

[error] 323-323: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: Strings must use singlequote.

(quotes)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0d7b080b1ff051dcfd6869d5f5e6487f02794de1 and a3c4be9fb8029eb3ccef1c257e17a2b7b00e0d13.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `app/src/wiki/mw/api/page.js` (2 hunks)
* `includes/ActionApi/ApiContentTranslationPublish.php` (7 hunks)
* `modules/mw.cx.MachineTranslationManager.js` (1 hunks)
* `modules/mw.cx.MachineTranslationService.js` (4 hunks)
* `modules/mw.cx.MwApiRequestManager.js` (1 hunks)
* `modules/mw.cx.TargetArticle.js` (6 hunks)
* `modules/mw.cx.TranslationController.js` (2 hunks)
* `modules/mw.cx.init.Translation.js` (3 hunks)
* `modules/mw.cx.init.js` (1 hunks)
* `modules/source/mw.cx.SelectedSourcePage.js` (2 hunks)
* `modules/ui/mw.cx.ui.ArticleColumn.js` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 ESLint</summary>

<details>
<summary>modules/mw.cx.init.js</summary>

[error] 66-66: Identifier 'tr_type' is not in camel case.

(camelcase)

</details>
<details>
<summary>modules/mw.cx.init.Translation.js</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 89-89: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 274-274: There must be a space after this paren.

(space-in-parens)

---

[error] 274-274: There must be a space before this paren.

(space-in-parens)

---

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 277-277: Identifier 'page_title' is not in camel case.

(camelcase)

---

[error] 277-277: 'page_title' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 278-278: Identifier 'user_name' is not in camel case.

(camelcase)

---

[error] 278-278: 'user_name' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 279-279: Identifier 'tr_type' is not in camel case.

(camelcase)

---

[error] 279-279: 'tr_type' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

---

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>
<details>
<summary>modules/mw.cx.TargetArticle.js</summary>

[error] 13-13: There must be a space after this paren.

(space-in-parens)

---

[error] 13-13: There must be a space before this paren.

(space-in-parens)

---

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

---

[error] 217-217: Unexpected console statement.

(no-console)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: Strings must use singlequote.

(quotes)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: Strings must use singlequote.

(quotes)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: Strings must use singlequote.

(quotes)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: Unexpected console statement.

(no-console)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 227-227: There must be a space after this paren.

(space-in-parens)

---

[error] 227-227: There must be a space before this paren.

(space-in-parens)

---

[error] 228-228: Unexpected var, use let or const instead.

(no-var)

---

[error] 229-229: There must be a space after this paren.

(space-in-parens)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: There must be a space before this paren.

(space-in-parens)

---

[error] 232-232: Unexpected var, use let or const instead.

(no-var)

---

[error] 232-232: Strings must use singlequote.

(quotes)

---

[error] 233-233: Unexpected var, use let or const instead.

(no-var)

---

[error] 233-233: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 233-233: Strings must use singlequote.

(quotes)

---

[error] 234-234: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: Unexpected console statement.

(no-console)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 238-238: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

---

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

---

[error] 248-248: There must be a space after this paren.

(space-in-parens)

---

[error] 248-248: Strings must use singlequote.

(quotes)

---

[error] 248-248: There must be a space before this paren.

(space-in-parens)

---

[error] 249-249: Unexpected var, use let or const instead.

(no-var)

---

[error] 249-249: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 256-256: There must be a space after this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 308-308: 'mddx' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 308-308: Strings must use singlequote.

(quotes)

---

[error] 310-310: 'mddxlink' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 310-310: 'mddxlink' is assigned a value but never used.

(no-unused-vars)

---

[error] 312-312: There must be a space after this paren.

(space-in-parens)

---

[error] 312-312: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 314-314: There must be a space after this paren.

(space-in-parens)

---

[error] 314-314: Strings must use singlequote.

(quotes)

---

[error] 314-314: There must be a space before this paren.

(space-in-parens)

---

[error] 316-316: There must be a space after this paren.

(space-in-parens)

---

[error] 316-316: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 320-320: There must be a space after this paren.

(space-in-parens)

---

[error] 320-320: There must be a space before this paren.

(space-in-parens)

---

[error] 321-321: There must be a space after this paren.

(space-in-parens)

---

[error] 321-321: There must be a space before this paren.

(space-in-parens)

---

[error] 322-322: There must be a space after this paren.

(space-in-parens)

---

[error] 322-322: There must be a space before this paren.

(space-in-parens)

---

[error] 323-323: There must be a space after this paren.

(space-in-parens)

---

[error] 323-323: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: Strings must use singlequote.

(quotes)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 435-435: 'wikiurl' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 436-436: There must be a space after this paren.

(space-in-parens)

---

[error] 436-436: There must be a space before this paren.

(space-in-parens)

---

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js</summary>

[error] 367-369: Unexpected var, use let or const instead.

(no-var)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space after this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 369-369: There must be a space before this paren.

(space-in-parens)

---

[error] 370-370: There must be a space after this paren.

(space-in-parens)

---

[error] 370-370: There must be a space before this paren.

(space-in-parens)

---

[error] 392-392: There must be a space after this paren.

(space-in-parens)

---

[error] 392-392: There must be a space before this paren.

(space-in-parens)

---

[error] 393-393: There must be a space after this paren.

(space-in-parens)

---

[error] 393-393: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 395-395: There must be a space after this paren.

(space-in-parens)

---

[error] 395-395: There must be a space before this paren.

(space-in-parens)

---

[error] 404-404: Unexpected tab character.

(no-tabs)

</details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js</summary>

[error] 156-156: There must be a space after this paren.

(space-in-parens)

---

[error] 156-156: Strings must use singlequote.

(quotes)

---

[error] 156-156: There must be a space before this paren.

(space-in-parens)

---

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: A space is required after '['.

(array-bracket-spacing)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: A space is required before ']'.

(array-bracket-spacing)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

---

[error] 170-170: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 173-173: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 173-173: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 173-173: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 178-178: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: Strings must use singlequote.

(quotes)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 187-187: There must be a space before this paren.

(space-in-parens)

---

[error] 187-188: Missing semicolon.

(semi)

---

[error] 189-190: Missing semicolon.

(semi)

---

[error] 191-191: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 194-194: There must be a space after this paren.

(space-in-parens)

---

[error] 194-194: Strings must use singlequote.

(quotes)

---

[error] 194-194: There must be a space before this paren.

(space-in-parens)

---

[error] 195-199: Unexpected var, use let or const instead.

(no-var)

---

[error] 198-198: Strings must use singlequote.

(quotes)

---

[error] 198-198: Unexpected trailing comma.

(comma-dangle)

---

[error] 199-200: Missing semicolon.

(semi)

---

[error] 203-204: Missing semicolon.

(semi)

---

[error] 205-205: Unexpected var, use let or const instead.

(no-var)

---

[error] 205-205: Strings must use singlequote.

(quotes)

---

[error] 205-205: There must be a space after this paren.

(space-in-parens)

---

[error] 205-205: There must be a space before this paren.

(space-in-parens)

---

[error] 205-206: Missing semicolon.

(semi)

---

[error] 207-207: There must be a space after this paren.

(space-in-parens)

---

[error] 207-207: There must be a space before this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: Unexpected console statement.

(no-console)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 214-214: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space after this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: Strings must use singlequote.

(quotes)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: Unexpected console statement.

(no-console)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 223-223: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 225-225: Unexpected console statement.

(no-console)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: There must be a space before this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 226-227: Missing semicolon.

(semi)

---

[error] 230-230: There must be a space after this paren.

(space-in-parens)

---

[error] 233-233: There must be a space before this paren.

(space-in-parens)

---

[error] 239-239: There must be a space after this paren.

(space-in-parens)

---

[error] 239-239: Strings must use singlequote.

(quotes)

---

[error] 239-239: There must be a space before this paren.

(space-in-parens)

---

[error] 240-244: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Strings must use singlequote.

(quotes)

---

[error] 243-243: Unexpected trailing comma.

(comma-dangle)

---

[error] 244-245: Missing semicolon.

(semi)

---

[error] 248-249: Missing semicolon.

(semi)

---

[error] 250-250: Unexpected var, use let or const instead.

(no-var)

---

[error] 250-250: Strings must use singlequote.

(quotes)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 252-252: There must be a space after this paren.

(space-in-parens)

---

[error] 252-252: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 255-255: Unexpected console statement.

(no-console)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: There must be a space before this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 334-335: Too many blank lines at the end of file. Max of 0 allowed.

(no-multiple-empty-lines)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>modules/ui/mw.cx.ui.ArticleColumn.js (1)</summary>

`48-48`: **Good enhancement to PageTitleWidget initialization**

Adding the language parameter to the PageTitleWidget constructor improves language context propagation through the UI components, which is important for the mdwiki support being implemented.

Consider adding tests to verify that this language parameter is correctly utilized by the PageTitleWidget class.

</details>
<details>
<summary>modules/source/mw.cx.SelectedSourcePage.js (1)</summary>

`391-399`: **Hard-hiding `en` & `simple` may surprise users**

Silently removing English/Simple English from the ULS list affects discoverability and breaks user expectations in multilingual environments.

Consider:

* Making this a feature flag / config option.
* At minimum, show a tooltip or grey them out instead of pure `display:none`.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 392-392: There must be a space after this paren.

(space-in-parens)

---

[error] 392-392: There must be a space before this paren.

(space-in-parens)

---

[error] 393-393: There must be a space after this paren.

(space-in-parens)

---

[error] 393-393: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space after this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 394-394: There must be a space before this paren.

(space-in-parens)

---

[error] 395-395: There must be a space after this paren.

(space-in-parens)

---

[error] 395-395: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</details>
<details>
<summary>includes/ActionApi/ApiContentTranslationPublish.php (1)</summary>

`175-178`: **Title override may break cross-wiki edits & namespace rules**  

Overwriting `$apiParams['title']` with `"{$params['to']}/{$params['sourcetitle']}"` ignores namespace prefixes and localisation. Consider building the Title via `Title::makeTitleSafe()` and using its `getPrefixedDBkey()`.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-10T02:15:53Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 11**

<details>
<summary>♻️ Duplicate comments (13)</summary><blockquote>

<details>
<summary>modules/mw.cx.MachineTranslationService.js (3)</summary><blockquote>

`155-159`: _⚠️ Potential issue_

**Hard-coded provider list for `or` bypasses cxserver contract**

Returning a hardcoded provider list (`["Google"]`) for the Oriya language short-circuits server capability detection. This creates maintenance issues:

1. It could drift out of sync when cxserver adds/removes providers
2. It hides non-Google options for Oriya if they become available

Consider querying the server (and caching) or gate this behind a feature flag.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

---

[error] 158-158: There must be a space after this paren.

(space-in-parens)

---

[error] 158-158: A space is required after '['.

(array-bracket-spacing)

---

[error] 158-158: Strings must use singlequote.

(quotes)

---

[error] 158-158: A space is required before ']'.

(array-bracket-spacing)

---

[error] 158-158: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`171-190`: _🛠️ Refactor suggestion_

**`cxtoken_error` mixes service layer with DOM manipulation**

This utility class should not directly manipulate DOM elements via jQuery selectors.

This violates separation of concerns and makes the service layer difficult to test. The proper approach is to:

1. Emit an event that UI layers can listen for
2. Reject the promise with a specific error code
3. Let UI code handle visual presentation

```diff
-mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
-
-	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
-	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
-	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
-
-	// wait 2 seconds then do the next line
-
-	setTimeout(function () {
-		$('.cx-message-widget-message').append(
-			$('<a>')
-				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
-				.attr('target', '_blank')
-				.text('Translation Dashboard')
-		);
-		$('.cx-message-widget-details').text(" Then refresh the page");
-
-	}, 2000)
-
+mw.cx.MachineTranslationService.prototype.handleTokenError = function () {
+	const loginUrl = 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login';
+	// Fire an event with all necessary information
+	mw.hook('mw.cx.tokenError').fire({
+		message: 'OAuth session expired, Please Login again',
+		loginUrl: loginUrl,
+		details: 'Then refresh the page'
+	});
+	
+	// For backward compatibility
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again');
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 171-171: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 174-174: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 174-174: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 174-174: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 175-175: There must be a space after this paren.

(space-in-parens)

---

[error] 175-175: There must be a space before this paren.

(space-in-parens)

---

[error] 175-175: There must be a space after this paren.

(space-in-parens)

---

[error] 175-175: There must be a space before this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 184-184: There must be a space after this paren.

(space-in-parens)

---

[error] 184-184: There must be a space before this paren.

(space-in-parens)

---

[error] 186-186: There must be a space after this paren.

(space-in-parens)

---

[error] 186-186: There must be a space before this paren.

(space-in-parens)

---

[error] 186-186: There must be a space after this paren.

(space-in-parens)

---

[error] 186-186: Strings must use singlequote.

(quotes)

---

[error] 186-186: There must be a space before this paren.

(space-in-parens)

---

[error] 188-188: There must be a space before this paren.

(space-in-parens)

---

[error] 188-189: Missing semicolon.

(semi)

</details>

</details>

---

`240-260`: _⚠️ Potential issue_

**`fetchCXServerToken` branch for `"mdwiki"` may break `getCXServerToken()` expectations**

The implementation for the `mdwiki` case has several issues:

1. No error handling for invalid responses
2. No validation of token structure
3. Inconsistent return format compared to the non-mdwiki branch

`getCXServerToken()` assumes the resolved object contains at least `{ age, jwt }`. The current code can lead to:
- `undefined` values if the network request fails
- `NaN` in arithmetic operations if `token.age` is missing
- Type errors when accessing properties

Proper error handling and validation are required:

```diff
if (this.sourceLanguage === "mdwiki") {
	const params = {
		user: mw.user.getName(),
		wiki: this.targetLanguage,
		ty: "cxtoken",
	};
	const options = {
		method: 'GET',
		headers: { 'Accept': 'application/json' }
	};

	const url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params);

	return fetch(url, options)
-		.then((response) => response.json())
+		.then((response) => {
+			if (!response.ok) {
+				throw new Error(`mdwiki token fetch failed: ${response.status}`);
+			}
+			return response.json();
+		})
+		.then((token) => {
+			if (!token || typeof token.age !== 'number' || !token.jwt) {
+				throw new Error('Invalid token format from mdwiki');
+			}
+			return token;
+		})
		.catch(error => {
			console.error('Error fetching mdwiki token:', error);
+			return Promise.reject(error);
		});
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 240-240: There must be a space after this paren.

(space-in-parens)

---

[error] 240-240: Strings must use singlequote.

(quotes)

---

[error] 240-240: There must be a space before this paren.

(space-in-parens)

---

[error] 241-245: Unexpected var, use let or const instead.

(no-var)

---

[error] 244-244: Strings must use singlequote.

(quotes)

---

[error] 244-244: Unexpected trailing comma.

(comma-dangle)

---

[error] 245-246: Missing semicolon.

(semi)

---

[error] 248-248: Unnecessarily quoted property 'Accept' found.

(quote-props)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 251-251: Unexpected var, use let or const instead.

(no-var)

---

[error] 251-251: Strings must use singlequote.

(quotes)

---

[error] 251-251: There must be a space after this paren.

(space-in-parens)

---

[error] 251-251: There must be a space before this paren.

(space-in-parens)

---

[error] 251-252: Missing semicolon.

(semi)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space before this paren.

(space-in-parens)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 256-256: Unexpected console statement.

(no-console)

---

[error] 256-256: There must be a space after this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 257-257: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>app/src/wiki/mw/api/page.js (1)</summary><blockquote>

`192-229`: _⚠️ Potential issue_

**Previous concerns still apply – unused var, non-standard options, and missing `response.ok` check**

The block re-introduces issues already pointed out in an earlier review:

* `targetWikiCode` is calculated but never used.  
* `dataType: 'json'` is a jQuery artefact – the Fetch API ignores it.  
* The promise always calls `response.json()` without verifying `response.ok`, so 4xx/5xx responses will throw.  
* Network failures bubble up as unhandled rejections.

Consider the previously suggested guarded pattern:

```diff
-const result = fetch(cxServerApiURL, options)
-    .then((response) => response.json())
-    .then((response) => response.segmentedContent);
+const result = fetch(cxServerApiURL, options)
+    .then((res) => {
+        if (!res.ok) {
+            throw new Error(`mdwiki fetch failed (${res.status})`);
+        }
+        return res.json();
+    })
+    .then((json) => {
+        if (!json.segmentedContent) {
+            throw new Error('mdwiki: missing segmentedContent in response');
+        }
+        return json.segmentedContent;
+    });
```

This makes failure modes explicit and prevents silent breakage upstream.

</blockquote></details>
<details>
<summary>modules/mw.cx.init.Translation.js (3)</summary><blockquote>

`88-98`: **Style & control-flow issues persist (== vs ===; missing early return)**

The block still uses loose equality, `var`, and continues after a fatal error, issues highlighted in the
previous review.  Consider adopting the earlier patch verbatim to avoid runtime failures.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`276-281`: **Avoid leaking real user names to external services**

Passing `mw.user.getName()` to `fetchSourcePageContent_mdwiki` exposes personally-identifiable information
to non-WMF endpoints, violating privacy expectations.  
Send an opaque token or omit the field unless explicit consent is obtained.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`396-401`: **Hard-coded admin list is brittle & fails linting**

Embedding user names (`admin_users`) in client code is fragile and breaks lint rules.  
Use MediaWiki’s permission checks instead, e.g.:

```js
if (mw.user.isAllowed('contenttranslation-admin')) { … }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.TargetArticle.js (3)</summary><blockquote>

`228-241`: _⚠️ Potential issue_

**Use safe property access and follow coding standards**

The code accesses properties without checking if they exist first. Additionally, use strict equality checks and follow the camelCase naming convention for variables.


```diff
-		var targeturl = publishResult.targeturl;
-		if (this.sourceLanguage === "mdwiki" && publishResult.published_to != "local") {
+		const targetUrl = publishResult.targeturl;
+		if (this.sourceLanguage === 'mdwiki' && publishResult.published_to !== 'local') {
-			targeturl = publishResult.targeturl_wiki;
+			targetUrl = publishResult.targeturl_wiki;
 		}
-		var qid = "";
-		var wd_result = "";
-		if (publishResult.LinkToWikidata) {
+		let qid = '';
+		let wdResult = '';
+		if (publishResult.LinkToWikidata) {
 			qid = publishResult.LinkToWikidata.qid;
-			console.log('LinkToWikidata: ' + JSON.stringify(publishResult.LinkToWikidata));
+			// Remove console.log in production code
 			// LinkToWikidata: {"result":"success","qid":"Q474070"}
-			wd_result = publishResult.LinkToWikidata.result;
+			wdResult = publishResult.LinkToWikidata.result;
 		}

-		this.translation.setTargetURL(targeturl);
+		this.translation.setTargetURL(targetUrl);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 228-228: Unexpected var, use let or const instead.

(no-var)

---

[error] 229-229: There must be a space after this paren.

(space-in-parens)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: There must be a space before this paren.

(space-in-parens)

---

[error] 232-232: Unexpected var, use let or const instead.

(no-var)

---

[error] 232-232: Strings must use singlequote.

(quotes)

---

[error] 233-233: Unexpected var, use let or const instead.

(no-var)

---

[error] 233-233: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 233-233: Strings must use singlequote.

(quotes)

---

[error] 234-234: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: Unexpected console statement.

(no-console)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 238-238: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`243-246`: _⚠️ Potential issue_

**Use safe property access to prevent potential crash**

This code unconditionally accesses `publishResult.save_result_all.mdwiki_result.edit.title` which will crash if any of these nested properties don't exist.


```diff
-		var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+		const newTitle = OO.getProp(publishResult, 'save_result_all', 'mdwiki_result', 'edit', 'title');
 		// mdwiki_result: {"warnings":{"main":{"*":"Unrecognized parameters: wpCaptchaId, wpCaptchaWord."}},"edit":{"new":"","result":"Success","pageid":9895285,"title":"مستخدم:Mr. Ibrahem/أوبلتوكسيماب","contentmodel":"wikitext","oldrevid":0,"newrevid":69736856,"newtimestamp":"2025-03-02T00:36:55Z","watched":""},"LinkToWikidata":{"error":"Cannot create link for namespace:2","nserror":"","qid":"Q7876570"}}

-		var done = this.publishComplete(new_title || null);
+		const done = this.publishComplete(newTitle || null);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`687-705`: _⚠️ Potential issue_

**Avoid XSS vulnerabilities by using DOM methods**

The current implementation builds HTML strings using template literals and directly injects them into the DOM, which poses an XSS risk. Instead, use jQuery or DOM methods to create and append elements.


```diff
mw.cx.TargetArticle.prototype.addMdwikiLinks = function (targetLanguage, targetTitle, qid, wd_result) {
	const pp = {
		lang: targetLanguage,
		title: targetTitle,
		save: 1
	};
-	var url = "https://mdwiki.toolforge.org/fixwikirefs.php?" + $.param(pp);
-	let link = `<a href='${url}' target='_blank'>Fix References</a>`
-
-	var wdlink = "";
-	if (qid != "" && qid != "undefined" && wd_result != "success") {
-		var target_wiki = targetLanguage + "wiki"
-		var wdurl = `https://www.wikidata.org/wiki/Special:SetSiteLink/${qid}/${target_wiki}?` + $.param({ page: targetTitle });
-		wdlink = ` - <a href='${wdurl}' target='_blank'>Link to Wikidata</a>`
-	}
-	var html = `<div style="float:left;">${link}${wdlink}</div>`;
-
-	$('.cx-message-widget-details').append(html);
+	const url = 'https://mdwiki.toolforge.org/fixwikirefs.php?' + $.param(pp);
+	
+	const $container = $('<div>').css('float', 'left');
+	
+	// Add "Fix References" link
+	$('<a>')
+		.attr({
+			href: url,
+			target: '_blank'
+		})
+		.text('Fix References')
+		.appendTo($container);
+	
+	// Add Wikidata link if applicable
+	if (qid !== '' && qid !== 'undefined' && wd_result !== 'success') {
+		const targetWiki = targetLanguage + 'wiki';
+		const wdurl = 'https://www.wikidata.org/wiki/Special:SetSiteLink/' + qid + '/' + targetWiki + '?' + 
+			$.param({ page: targetTitle });
+		
+		$('<span>').text(' - ').appendTo($container);
+		$('<a>')
+			.attr({
+				href: wdurl,
+				target: '_blank'
+			})
+			.text('Link to Wikidata')
+			.appendTo($container);
+	}
+	
+	$('.cx-message-widget-details').append($container);
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>includes/ActionApi/ApiContentTranslationPublish.php (3)</summary><blockquote>

`45-89`: _⚠️ Potential issue_

**Move `post_to_target` inside the class & enable TLS verification**

Placing a global namespaced function inside this file pollutes the global namespace. More importantly, there's no TLS verification enabled in the cURL request, which creates a security vulnerability.



The function should:
1. Be moved as a private method inside the class
2. Follow PSR coding standards (camelCase method name)
3. Enable TLS verification
4. Use proper type hints

```diff
-function post_to_target($params)
-{
-	// $url = 'https://mdwiki.toolforge.org/Translation_Dashboard/publish/main.php';
-	$url = 'https://mdwiki.toolforge.org/publish/main.php';
-	$ch = curl_init();
-
-	// if ($ch === false) {
-	// 	throw new \RuntimeException('curl_init() failed');
-	// }
-
-	$usr_agent = "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)";
-
-	curl_setopt($ch, CURLOPT_URL, $url);
-	curl_setopt($ch, CURLOPT_POST, 1);
-	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
-
-	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
-	// mdwiki_result: {"response":"Requests must have a user agent"}
-	curl_setopt($ch, CURLOPT_USERAGENT, $usr_agent);
-	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
-	curl_setopt($ch, CURLOPT_TIMEOUT, 15);
-
-	$response = curl_exec($ch);
-
-	$curlErr   = curl_error($ch);
-	$status    = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
-	curl_close($ch);
-
-	if ($response === false) {
-		return ['error' => $curlErr ?: 'Unknown cURL error', 'response' => $response];
-	}
-
-	if ($status !== 200) {
-		return ['error' => "Unexpected HTTP status $status", 'response' => $response];
-	}
-
-	$js = json_decode($response, true);
-
-	if ($js === null) {
-		return ['error' => 'Invalid JSON', 'response' => $response];
-	}
-
-	return $js ?? ['response' => $response];
-}
```

Add this inside the class instead:

```php
/**
 * Send a POST request to the mdwiki external service
 *
 * @param array $params Parameters to send to the external service
 * @return array Response from the external service
 */
private function postToTarget(array $params): array {
    $url = 'https://mdwiki.toolforge.org/publish/main.php';
    $ch = curl_init();

    $userAgent = "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)";

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => http_build_query($params),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_USERAGENT => $userAgent,
        CURLOPT_CONNECTTIMEOUT => 15,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
    ]);

    $response = curl_exec($ch);
    $curlErr = curl_error($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($response === false) {
        return ['error' => $curlErr ?: 'Unknown cURL error', 'response' => $response];
    }

    if ($status !== 200) {
        return ['error' => "Unexpected HTTP status $status", 'response' => $response];
    }

    $js = json_decode($response, true);

    if ($js === null) {
        return ['error' => 'Invalid JSON', 'response' => $response];
    }

    return $js ?? ['response' => $response];
}
```

This change will require also updating the call in `publishToMdwiki` to use `$this->postToTarget($t_Params)`.

---

`122-122`: _⚠️ Potential issue_

**Dynamic property `$published_to` breaks on PHP 8.2**

Accessing undeclared properties is deprecated in PHP 8.2+. Declare a typed property using camelCase naming convention.


```diff
-		$this->published_to = "local";
```

Add this property declaration at the top of the class:
```php
/** @var string */
private string $publishedTo = 'local';
```

And replace all instances of `$this->published_to` with `$this->publishedTo`.

---

`392-395`: _🛠️ Refactor suggestion_

**Fix property naming and use consistent styling**

Use the proper property name and consistent string quotes.

```diff
 				'result' => 'success',
 				'targeturl' => $targetURL,
 				'targeturl_wiki' => $targeturl_wiki,
-				'published_to' => $this->published_to
+				'published_to' => $this->publishedTo
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (18)</summary><blockquote>

<details>
<summary>modules/ui/widgets/mw.cx.ui.PageTitleWidget.js (1)</summary><blockquote>

`95-95`: **Improve variable naming for better semantics**

The rename from `href2` to `wikipediaUrl` makes the code more readable by clearly indicating the purpose of the variable.


However, there are style violations that should be addressed:
```diff
-var wikipediaUrl = "//" + this.language + ".wikipedia.org/wiki/" + encodeURIComponent( this.getValue() );
+const wikipediaUrl = '//' + this.language + '.wikipedia.org/wiki/' + encodeURIComponent( this.getValue() );
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 95-95: Unexpected var, use let or const instead.

(no-var)

---

[error] 95-95: Strings must use singlequote.

(quotes)

---

[error] 95-95: Strings must use singlequote.

(quotes)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/base/mw.cx.SiteMapper.js (2)</summary><blockquote>

`86-89`: **Code refactoring - simplified conditional logic**

The conditional assignment has been refactored using a ternary operator, making the code more concise and readable.


There are style violations that should be addressed:
```diff
-	const url = language === 'mdwiki'
-		? this.SiteTemplates_mdwiki.api
-		: this.siteTemplates.api.replace('$1', domain);
+	const url = language === 'mdwiki' ?
+		this.SiteTemplates_mdwiki.api :
+		this.siteTemplates.api.replace('$1', domain);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 87-87: '?' should be placed at the end of the line.

(operator-linebreak)

---

[error] 88-88: ':' should be placed at the end of the line.

(operator-linebreak)

---

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`130-133`: **Code refactoring - simplified conditional logic**

Similar to the previous ternary refactoring, this change improves code conciseness and readability.


The same style violations need to be addressed here as well:
```diff
-	const templates = language === 'mdwiki'
-		? this.SiteTemplates_mdwiki
-		: this.siteTemplates;
+	const templates = language === 'mdwiki' ?
+		this.SiteTemplates_mdwiki :
+		this.siteTemplates;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 131-131: '?' should be placed at the end of the line.

(operator-linebreak)

---

[error] 132-132: ':' should be placed at the end of the line.

(operator-linebreak)

</details>

</details>

</blockquote></details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js (1)</summary><blockquote>

`18-21`: **Remove commented-out code**

Commented-out code clutters the codebase and creates confusion about intended behavior. Either implement this functionality properly or remove the comments entirely.

```diff
-
-	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
-	// TODO: mdwiki work
-
```

</blockquote></details>
<details>
<summary>modules/mw.cx.TranslationMdwiki.js (2)</summary><blockquote>

`49-55`: **`dataType: 'json'` is ignored by Fetch**

`dataType` is a jQuery option and has no effect here.  
Leaving it in is harmless but misleading to future maintainers.  
You can safely drop the property.

---

`199-217`: **Inefficient DOM walk & string replace in `removeUnlinkedWikibase`**

The function parses HTML into DOM *and* performs string replacement on the original HTML inside the same loop.  
Parsing is already O(n); walking the resulting DOM and then calling `html.replace` for each element adds extra
work and risks corrupting indices.

Simpler & safer: operate either on the DOM *or* the string, not both:

```diff
-Array.from(elements).forEach(element => {
-    const lowerOuterHtml = element.outerHTML.toLowerCase();
-    if (lowerOuterHtml.includes("unlinkedwikibase") || lowerOuterHtml.includes("mdwiki revid")) {
-        html = html.replace(element.outerHTML, '');
-    }
-});
-return html;
+elements.forEach(el => el.remove());
+return dom.documentElement.outerHTML;
```

</blockquote></details>
<details>
<summary>modules/mw.cx.TargetArticle.js (4)</summary><blockquote>

`18-18`: **Fix syntax error: double semicolon**

There is an unnecessary extra semicolon at the end of this line.

```diff
-	this.campaign = config.campaign || '';;
+	this.campaign = config.campaign || '';
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 18-18: Missing whitespace after semicolon.

(semi-spacing)

---

[error] 18-18: Unnecessary semicolon.

(no-extra-semi)

</details>

</details>

---

`151-151`: **Remove commented out code**

This commented-out code should be removed if it's no longer needed.

```diff
-			// user: mw.user.getName(),
```

---

`308-329`: **Refactor OAuth error handling for better maintainability**

The OAuth error handling code uses string literals instead of using the MediaWiki message system. Also, it creates DOM elements directly which is good, but the overall structure can be improved.

```diff
-	let mddx = "OAuth session expired, Please Log again to Translation Dashboard";
-	// cx-message-widget-message
-	let mddxlink = "OAuth session expired, Please Log again to <a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	const oauthExpiredMsg = 'OAuth session expired, Please log in again to Translation Dashboard';
+	const authUrl = 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login';
 	// {"result":"error","edit":{"error":"noaccess","username":"Mr. Ibrahem"}}
 	if (data.edit.error) {
 		if (data.edit.error === 'noaccess' || (data.edit.error && data.edit.error.code === 'noaccess')) {
-			this.showPublishError(mddx, "no access_keys in Translation_Dashboard");
-			// $('.cx-message-widget-message').html(mddxlink)
-			$('.cx-message-widget-message')
-				.empty()
-				.append(
-					$('<span>').text('OAuth session expired, Please Log again in '),
-					$('<a>')
-						.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
-						.attr('target', '_blank')
-						.text('Translation Dashboard')
-				);
-			$('.cx-message-widget-details').html(" Then refresh the page");
-			// $('.cx-message-widget-details').html("<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>")
+			this.showPublishError(oauthExpiredMsg, 'No access keys in Translation Dashboard');
+			
+			const $message = $('.cx-message-widget-message');
+			if ($message.length) {
+				$message
+					.empty()
+					.append(
+						$('<span>').text('OAuth session expired, Please log in again in '),
+						$('<a>')
+							.attr({
+								href: authUrl,
+								target: '_blank'
+							})
+							.text('Translation Dashboard')
+					);
+			}
+			
+			const $details = $('.cx-message-widget-details');
+			if ($details.length) {
+				$details.text('Then refresh the page');
+			}
 			return;
 		}
 	}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 308-308: 'mddx' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 308-308: Strings must use singlequote.

(quotes)

---

[error] 310-310: 'mddxlink' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 310-310: 'mddxlink' is assigned a value but never used.

(no-unused-vars)

---

[error] 312-312: There must be a space after this paren.

(space-in-parens)

---

[error] 312-312: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 314-314: There must be a space after this paren.

(space-in-parens)

---

[error] 314-314: Strings must use singlequote.

(quotes)

---

[error] 314-314: There must be a space before this paren.

(space-in-parens)

---

[error] 316-316: There must be a space after this paren.

(space-in-parens)

---

[error] 316-316: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 320-320: There must be a space after this paren.

(space-in-parens)

---

[error] 320-320: There must be a space before this paren.

(space-in-parens)

---

[error] 321-321: There must be a space after this paren.

(space-in-parens)

---

[error] 321-321: There must be a space before this paren.

(space-in-parens)

---

[error] 322-322: There must be a space after this paren.

(space-in-parens)

---

[error] 322-322: There must be a space before this paren.

(space-in-parens)

---

[error] 323-323: There must be a space after this paren.

(space-in-parens)

---

[error] 323-323: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: Strings must use singlequote.

(quotes)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`435-436`: **Use const for variables that aren't reassigned**

The variable `wikiurl` is never reassigned, so it should use `const` instead of `let`.

```diff
-		let wikiurl = "https://" + this.targetLanguage + ".wikipedia.org";
-		this.captchaDialog.setFancyCaptcha(wikiurl + apiResult.url);
+		const wikiurl = 'https://' + this.targetLanguage + '.wikipedia.org';
+		this.captchaDialog.setFancyCaptcha(wikiurl + apiResult.url);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 435-435: 'wikiurl' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 436-436: There must be a space after this paren.

(space-in-parens)

---

[error] 436-436: There must be a space before this paren.

(space-in-parens)

</details>

</details>

</blockquote></details>
<details>
<summary>includes/ActionApi/ApiContentTranslationPublish.php (8)</summary><blockquote>

`198-198`: **Use consistent code style for conditional expressions**

Simplify the ternary expression and use consistent variable naming.

```diff
-			'title' => ($params['from'] === 'mdwiki') ? $params['to'] . "/" . $params['sourcetitle'] : $title->getPrefixedDBkey(),
+			'title' => ($params['from'] === 'mdwiki') 
+				? $params['to'] . '/' . $params['sourcetitle'] 
+				: $title->getPrefixedDBkey(),
```

---

`370-371`: **Consistent variable naming**

Continue with consistent variable naming.

```diff
-		$save_edit = $saveresult['edit'] ?? [];
-		$editStatus = $saveresult['edit']['result'] ?? [];
+		$saveEdit = $saveResult['edit'] ?? [];
+		$editStatus = $saveResult['edit']['result'] ?? [];
```

---

`374-378`: **Consistent variable naming**

Continue with consistent variable naming.

```diff
-			if (isset($save_edit['newrevid'])) {
+			if (isset($saveEdit['newrevid'])) {
 				$tags = $this->getTags($params);
 				// Add the tags post-send, after RC row insertion
-				$revId = intval($save_edit['newrevid']);
+				$revId = intval($saveEdit['newrevid']);
 				DeferredUpdates::addCallableUpdate(function () use ($revId, $tags) {
 					$this->changeTagsStore->addTags($tags, null, $revId, null);
```

---

`383-389`: **Fix style issues and remove unnecessary semicolon**

Fix string quotes and remove unnecessary semicolon.

```diff
-			if ($params['from'] === "mdwiki") {
-				$title2 = $params['to'] . "/" . $params['sourcetitle'];
-			};
+			if ($params['from'] === 'mdwiki') {
+				$title2 = $params['to'] . '/' . $params['sourcetitle'];
+			}

 			$targetURL = $this->targetUrlCreator->createTargetUrl($title2, $params['to']);
 			$targeturl_wiki = SiteMapper::getPageURL($params['to'], $targetTitle->getPrefixedDBkey());
```

---

`397-399`: **Remove unnecessary semicolon**

Remove unnecessary semicolon at the end of the conditional block.

```diff
 			if (is_array($saveresult) && isset($saveresult['LinkToWikidata'])) {
 				$result['LinkToWikidata'] = $saveresult['LinkToWikidata'];
-			};
+			}
```

---

`404-406`: **Consistent variable naming**

Continue with consistent variable naming.

```diff
-			if (isset($save_edit['newrevid'])) {
-				$result['newrevid'] = intval($save_edit['newrevid']);
+			if (isset($saveEdit['newrevid'])) {
+				$result['newrevid'] = intval($saveEdit['newrevid']);
 				$this->translation->translation['targetRevisionId'] = $result['newrevid'];
```

---

`417-417`: **Consistent variable naming**

Continue with consistent variable naming.

```diff
-				'edit' => $save_edit ?? []
+				'edit' => $saveEdit ?? []
```

---

`420-420`: **Consistent variable naming**

Continue with consistent variable naming.

```diff
-		$result['save_result_all'] = $save_result_all;
+		$result['save_result_all'] = $saveResultAll;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a3c4be9fb8029eb3ccef1c257e17a2b7b00e0d13 and f7d672f38fc4b5a8179e0d6a81ef69e714712b84.

</details>

<details>
<summary>📒 Files selected for processing (12)</summary>

* `app/src/wiki/mw/api/page.js` (7 hunks)
* `includes/ActionApi/ApiContentTranslationPublish.php` (7 hunks)
* `modules/base/mw.cx.SiteMapper.js` (4 hunks)
* `modules/mw.cx.MachineTranslationManager.js` (1 hunks)
* `modules/mw.cx.MachineTranslationService.js` (4 hunks)
* `modules/mw.cx.MwApiRequestManager.js` (1 hunks)
* `modules/mw.cx.TargetArticle.js` (6 hunks)
* `modules/mw.cx.TranslationController.js` (2 hunks)
* `modules/mw.cx.TranslationMdwiki.js` (1 hunks)
* `modules/mw.cx.init.Translation.js` (3 hunks)
* `modules/ui/widgets/mw.cx.ui.PageTitleWidget.js` (1 hunks)
* `modules/ve-cx/init/ve.init.mw.CXTarget.js` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* modules/mw.cx.MachineTranslationManager.js
* modules/mw.cx.MwApiRequestManager.js
* modules/mw.cx.TranslationController.js

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>app/src/wiki/mw/api/page.js (2)</summary><blockquote>

<details>
<summary>modules/mw.cx.MachineTranslationService.js (4)</summary>

* `options` (201-204)
* `options` (246-249)
* `result` (208-227)
* `result` (253-257)

</details>
<details>
<summary>modules/base/mw.cx.SiteMapper.js (1)</summary>

* `options` (313-318)

</details>

</blockquote></details>
<details>
<summary>modules/ui/widgets/mw.cx.ui.PageTitleWidget.js (1)</summary><blockquote>

<details>
<summary>includes/Special/SpecialContentTranslation.php (1)</summary>

* `encodeURIComponent` (123-126)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>modules/base/mw.cx.SiteMapper.js</summary>

[error] 87-87: '?' should be placed at the end of the line.

(operator-linebreak)

---

[error] 88-88: ':' should be placed at the end of the line.

(operator-linebreak)

---

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 131-131: '?' should be placed at the end of the line.

(operator-linebreak)

---

[error] 132-132: ':' should be placed at the end of the line.

(operator-linebreak)

---

[error] 178-178: Expected indentation of 1 tab but found 2.

(indent)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 212-212: Unexpected trailing comma.

(comma-dangle)

</details>
<details>
<summary>modules/mw.cx.MachineTranslationService.js</summary>

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

---

[error] 158-158: There must be a space after this paren.

(space-in-parens)

---

[error] 158-158: A space is required after '['.

(array-bracket-spacing)

---

[error] 158-158: Strings must use singlequote.

(quotes)

---

[error] 158-158: A space is required before ']'.

(array-bracket-spacing)

---

[error] 158-158: There must be a space before this paren.

(space-in-parens)

---

[error] 171-171: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 174-174: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 174-174: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 174-174: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 175-175: There must be a space after this paren.

(space-in-parens)

---

[error] 175-175: There must be a space before this paren.

(space-in-parens)

---

[error] 175-175: There must be a space after this paren.

(space-in-parens)

---

[error] 175-175: There must be a space before this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 184-184: There must be a space after this paren.

(space-in-parens)

---

[error] 184-184: There must be a space before this paren.

(space-in-parens)

---

[error] 186-186: There must be a space after this paren.

(space-in-parens)

---

[error] 186-186: There must be a space before this paren.

(space-in-parens)

---

[error] 186-186: There must be a space after this paren.

(space-in-parens)

---

[error] 186-186: Strings must use singlequote.

(quotes)

---

[error] 186-186: There must be a space before this paren.

(space-in-parens)

---

[error] 188-188: There must be a space before this paren.

(space-in-parens)

---

[error] 188-189: Missing semicolon.

(semi)

---

[error] 190-191: Missing semicolon.

(semi)

---

[error] 192-192: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 195-195: There must be a space after this paren.

(space-in-parens)

---

[error] 195-195: Strings must use singlequote.

(quotes)

---

[error] 195-195: There must be a space before this paren.

(space-in-parens)

---

[error] 196-200: Unexpected var, use let or const instead.

(no-var)

---

[error] 199-199: Strings must use singlequote.

(quotes)

---

[error] 199-199: Unexpected trailing comma.

(comma-dangle)

---

[error] 200-201: Missing semicolon.

(semi)

---

[error] 203-203: Unnecessarily quoted property 'Accept' found.

(quote-props)

---

[error] 204-205: Missing semicolon.

(semi)

---

[error] 206-206: Unexpected var, use let or const instead.

(no-var)

---

[error] 206-206: Strings must use singlequote.

(quotes)

---

[error] 206-206: There must be a space after this paren.

(space-in-parens)

---

[error] 206-206: There must be a space before this paren.

(space-in-parens)

---

[error] 206-207: Missing semicolon.

(semi)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 211-211: Unexpected console statement.

(no-console)

---

[error] 211-211: There must be a space after this paren.

(space-in-parens)

---

[error] 211-211: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 226-226: Unexpected console statement.

(no-console)

---

[error] 226-226: There must be a space after this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 227-227: There must be a space before this paren.

(space-in-parens)

---

[error] 227-228: Missing semicolon.

(semi)

---

[error] 231-231: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 240-240: There must be a space after this paren.

(space-in-parens)

---

[error] 240-240: Strings must use singlequote.

(quotes)

---

[error] 240-240: There must be a space before this paren.

(space-in-parens)

---

[error] 241-245: Unexpected var, use let or const instead.

(no-var)

---

[error] 244-244: Strings must use singlequote.

(quotes)

---

[error] 244-244: Unexpected trailing comma.

(comma-dangle)

---

[error] 245-246: Missing semicolon.

(semi)

---

[error] 248-248: Unnecessarily quoted property 'Accept' found.

(quote-props)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 251-251: Unexpected var, use let or const instead.

(no-var)

---

[error] 251-251: Strings must use singlequote.

(quotes)

---

[error] 251-251: There must be a space after this paren.

(space-in-parens)

---

[error] 251-251: There must be a space before this paren.

(space-in-parens)

---

[error] 251-252: Missing semicolon.

(semi)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space before this paren.

(space-in-parens)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 256-256: Unexpected console statement.

(no-console)

---

[error] 256-256: There must be a space after this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 257-257: There must be a space before this paren.

(space-in-parens)

---

[error] 335-336: Too many blank lines at the end of file. Max of 0 allowed.

(no-multiple-empty-lines)

</details>
<details>
<summary>modules/mw.cx.TargetArticle.js</summary>

[error] 13-13: There must be a space after this paren.

(space-in-parens)

---

[error] 13-13: There must be a space before this paren.

(space-in-parens)

---

[error] 18-18: Missing whitespace after semicolon.

(semi-spacing)

---

[error] 18-18: Unnecessary semicolon.

(no-extra-semi)

---

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

---

[error] 217-217: Unexpected console statement.

(no-console)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: Strings must use singlequote.

(quotes)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: Strings must use singlequote.

(quotes)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: Strings must use singlequote.

(quotes)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: Unexpected console statement.

(no-console)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 227-227: There must be a space after this paren.

(space-in-parens)

---

[error] 227-227: There must be a space before this paren.

(space-in-parens)

---

[error] 228-228: Unexpected var, use let or const instead.

(no-var)

---

[error] 229-229: There must be a space after this paren.

(space-in-parens)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: There must be a space before this paren.

(space-in-parens)

---

[error] 232-232: Unexpected var, use let or const instead.

(no-var)

---

[error] 232-232: Strings must use singlequote.

(quotes)

---

[error] 233-233: Unexpected var, use let or const instead.

(no-var)

---

[error] 233-233: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 233-233: Strings must use singlequote.

(quotes)

---

[error] 234-234: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: Unexpected console statement.

(no-console)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 238-238: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

---

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

---

[error] 248-248: There must be a space after this paren.

(space-in-parens)

---

[error] 248-248: Strings must use singlequote.

(quotes)

---

[error] 248-248: There must be a space before this paren.

(space-in-parens)

---

[error] 249-249: Unexpected var, use let or const instead.

(no-var)

---

[error] 249-249: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 256-256: There must be a space after this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

---

[error] 308-308: 'mddx' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 308-308: Strings must use singlequote.

(quotes)

---

[error] 310-310: 'mddxlink' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 310-310: 'mddxlink' is assigned a value but never used.

(no-unused-vars)

---

[error] 312-312: There must be a space after this paren.

(space-in-parens)

---

[error] 312-312: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space after this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 313-313: There must be a space before this paren.

(space-in-parens)

---

[error] 314-314: There must be a space after this paren.

(space-in-parens)

---

[error] 314-314: Strings must use singlequote.

(quotes)

---

[error] 314-314: There must be a space before this paren.

(space-in-parens)

---

[error] 316-316: There must be a space after this paren.

(space-in-parens)

---

[error] 316-316: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 319-319: There must be a space after this paren.

(space-in-parens)

---

[error] 319-319: There must be a space before this paren.

(space-in-parens)

---

[error] 320-320: There must be a space after this paren.

(space-in-parens)

---

[error] 320-320: There must be a space before this paren.

(space-in-parens)

---

[error] 321-321: There must be a space after this paren.

(space-in-parens)

---

[error] 321-321: There must be a space before this paren.

(space-in-parens)

---

[error] 322-322: There must be a space after this paren.

(space-in-parens)

---

[error] 322-322: There must be a space before this paren.

(space-in-parens)

---

[error] 323-323: There must be a space after this paren.

(space-in-parens)

---

[error] 323-323: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 325-325: There must be a space after this paren.

(space-in-parens)

---

[error] 325-325: Strings must use singlequote.

(quotes)

---

[error] 325-325: There must be a space before this paren.

(space-in-parens)

---

[error] 435-435: 'wikiurl' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 435-435: Strings must use singlequote.

(quotes)

---

[error] 436-436: There must be a space after this paren.

(space-in-parens)

---

[error] 436-436: There must be a space before this paren.

(space-in-parens)

---

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>
<details>
<summary>modules/mw.cx.init.Translation.js</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 274-274: There must be a space after this paren.

(space-in-parens)

---

[error] 274-274: There must be a space before this paren.

(space-in-parens)

---

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

---

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>
<details>
<summary>modules/ui/widgets/mw.cx.ui.PageTitleWidget.js</summary>

[error] 95-95: Unexpected var, use let or const instead.

(no-var)

---

[error] 95-95: Strings must use singlequote.

(quotes)

---

[error] 95-95: Strings must use singlequote.

(quotes)

</details>

</details>
<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/src/wiki/mw/api/page.js</summary>

[error] 40-40: Avoid the use of spread (`...`) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in `.reduce`) because it causes a time complexity of `O(n^2)`.
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

---

[error] 49-49: Avoid the use of spread (`...`) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in `.reduce`) because it causes a time complexity of `O(n^2)`.
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>modules/ve-cx/init/ve.init.mw.CXTarget.js (3)</summary>

`1123-1123`: **Improved parameter naming**

The parameter rename from `set_it` to `applyImmediately` greatly improves readability and provides better context about the parameter's purpose.

---

`1129-1133`: **Added null check for defensive programming**

The addition of a null check for `sectionNode` before calling `changeContentSource` prevents potential runtime errors if the section node doesn't exist.

---

`538-542`: **Improved loop formatting**

The loop formatting has been updated for better consistency. The use of arrow functions with the timeout is appropriate for this use case.

</details>
<details>
<summary>modules/base/mw.cx.SiteMapper.js (2)</summary>

`177-181`: **Improved string checking logic**

The change from `indexOf` to `endsWith` improves the precision of the check and makes the intent clearer. Using a regex replacement to only replace the suffix is also more precise than the previous approach.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 178-178: Expected indentation of 1 tab but found 2.

(indent)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

</details>

</details>

---

`212-212`: **Use dynamic data source instead of hardcoded values**

This change properly uses the server-provided source languages instead of a hardcoded array. This improves maintainability and correctness as the available languages can change.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 212-212: Unexpected trailing comma.

(comma-dangle)

</details>

</details>

</details>
<details>
<summary>app/src/wiki/mw/api/page.js (1)</summary>

`246-256`: **Typo in CX-server parameter keys will break substitution**

Keys are prefixed with `$` (`$sourcelanguage`, `$targetlanguage`, …) but the *values* do **not** include them in `relativeApiURL`.  
If `siteMapper.getCXServerUrl` performs string replacement, the `$` in the object keys is unnecessary.  
Please confirm the expected placeholder syntax – otherwise the constructed URL may contain literal “$” tokens.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-10T02:42:08Z

Review comments:
diff_hunk: @@ -22,6 +22,7 @@
  */
 mw.cx.MwApiRequestManager = function MwCxMwApiRequestManager( sourceLanguage, targetLanguage, siteMapper ) {
 	this.sourceLanguage = sourceLanguage;
+	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
path: modules/mw.cx.MwApiRequestManager.js
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider uncommenting the mdwiki handling logic**

This line appears to be part of the special handling for "mdwiki" source language mentioned in the PR objectives. Similar commented code appears in other modules as well. If this is intentional for future implementation, consider adding a TODO comment explaining when and why this should be uncommented.

If this is intended to be active code, it should be uncommented to ensure proper handling of mdwiki source language.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for similar commented mdwiki handling code patterns across the codebase
echo "Searching for similar commented mdwiki handling patterns:"
rg -A 1 -B 1 "// if \(.*=== 'mdwiki'\)"

echo "Checking for active (uncommented) mdwiki handling:"
rg "if \(.*=== 'mdwiki'\)" --no-comments
```

Length of output: 2203

---



</details>

**Uncomment or document mdwiki handling logic across modules**

The special‐case handling for `mdwiki` is currently commented out in multiple places. If full support for `mdwiki` is required now, these lines should be uncommented; otherwise, please add a clear TODO explaining why they’re disabled and when they’ll be re-enabled.

Affected locations:
- modules/mw.cx.MwApiRequestManager.js  
- modules/mw.cx.MachineTranslationManager.js  
- modules/ui/mw.cx.ui.TranslationView.js  
- modules/mw.cx.MachineTranslationService.js  
- modules/ve-cx/ui/ve.ui.CXLinkContextItem.js  
- modules/cache/mw.cx.TitlePairCache.js

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 749d990 to f7d672f
created_at: 2025-05-10T02:03:31Z
author_association: NONE
diff_hunk: @@ -21,6 +21,8 @@ mw.cx.MachineTranslationManager = function MwCxMachineTranslationManager(
 	sourceLanguage, targetLanguage, MTService
 ) {
 	this.sourceLanguage = sourceLanguage;
+
+	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
path: modules/mw.cx.MachineTranslationManager.js
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Maintain consistency in mdwiki handling implementation**

This commented code is identical to what's found in MwApiRequestManager.js. For consistent behavior, ensure that all mdwiki-related code is either commented or uncommented across all files.

If this is intended to be active code for the "mdwiki" update mentioned in the PR objectives, it should be uncommented and tested thoroughly.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any active implementations of mdwiki handling
echo "Checking for active mdwiki handling in the codebase:"
rg "if \(.*sourceLanguage.*=== 'mdwiki'\)" --no-comments

echo "Looking for mdwiki-specific code in active functions:"
rg "mdwiki" | grep -v "//"
```

Length of output: 4603

---



</details>

**Ensure consistent mdwiki→en mapping in MachineTranslationManager.js**

The codebase applies special handling for “mdwiki” in several places (e.g. MachineTranslationService.js, SiteMapper.js, TargetArticle.js), but in modules/mw.cx.MachineTranslationManager.js the mapping is currently commented out. This will cause translation requests for mdwiki sources to be sent as “mdwiki” (and likely rejected) rather than “en.”  

• File: modules/mw.cx.MachineTranslationManager.js  
  Line 25:  
  ```diff
  - // if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
  + if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
  ```  
• After uncommenting, add or update unit/integration tests to verify that when sourceLanguage==="mdwiki" it is remapped to “en” before making the MT request.  


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
-	// if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
+		if (sourceLanguage === 'mdwiki') { this.sourceLanguage = 'en'; }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:31Z
author_association: NONE
diff_hunk: @@ -59,10 +59,11 @@
 		services = {
 			siteMapper: mw.cx.siteMapper
 		};
-
 		services.requestManager = new mw.cx.MwApiRequestManager( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.siteMapper );
 		services.MTService = new mw.cx.MachineTranslationService( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.siteMapper );
 		services.MTManager = new mw.cx.MachineTranslationManager( mw.cx.sourceLanguage, mw.cx.targetLanguage, services.MTService );
+		services.campaign = query.campaign;
+		services.tr_type = query.tr_type;
path: modules/mw.cx.init.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Fix variable naming convention and consider validation**

The variable `tr_type` violates JavaScript's camelCase naming convention. Additionally, neither of these variables have validation or documentation explaining their purpose.


```diff
-services.campaign = query.campaign;
-services.tr_type = query.tr_type;
+// Add campaign information for tracking
+services.campaign = query.campaign || null;
+// Translation type parameter (camelCase naming convention)
+services.trType = query.tr_type || null;
```

While the campaign parameter is used later in the code (lines 74-76), there's no usage of `tr_type`. Ensure this new property is actually utilized somewhere.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// Add campaign information for tracking
services.campaign = query.campaign || null;
// Translation type parameter (camelCase naming convention)
services.trType = query.tr_type || null;
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 66-66: Identifier 'tr_type' is not in camel case.

(camelcase)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -14,6 +14,7 @@
 mw.cx.TranslationController = function MwCxTranslationController(
 	translation, veTarget, siteMapper, config
 ) {
+	this.config = config;
 	this.translation = translation;
 	this.veTarget = veTarget;
 	this.siteMapper = siteMapper;
path: modules/mw.cx.TranslationController.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Ensure `config` is always an object to avoid a runtime TypeError**

`config` is documented as optional, yet `this.config.campaign` is dereferenced unconditionally.  
If `config` is `undefined`, the constructor will throw when it reaches line 46.

```diff
-mw.cx.TranslationController = function MwCxTranslationController(
-	translation, veTarget, siteMapper, config
-) {
-	this.config = config;
+mw.cx.TranslationController = function MwCxTranslationController(
+	translation, veTarget, siteMapper, config = {}
+) {
+	this.config = config;          // always an object
```

This one-liner guards every subsequent access (`this.config.campaign`, `new mw.cx.TranslationTracker( …, config )`, etc.) without changing call sites.  




Also applies to: 45-47

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -179,6 +179,55 @@ const fetchPageContent = (
   );
 };
 
+/**
+ * Fetches segmented content of a page for given source language,
+ * target language and source title.
+ * @param {string} sourceLanguage
+ * @param {string} targetLanguage
+ * @param {string} sourceTitle
+ * @param {string|null} revision
+ * @return {Promise<String>}
+ */
+const fetchSegmentedContent_mdwiki = (
+  sourceLanguage,
+  targetLanguage,
+  sourceTitle,
+  revision = null
+) => {
+  const title = sourceTitle.replace(/ /g, "_")
+  const sourceWikiCode = sourceLanguage;
+  const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
+
+  const cxServerParams = {
+    sourcelanguage: sourceWikiCode,
+    targetlanguage: targetWikiCode,
+    section0: 1,
+  };
+
+  // var cxServerApiURL = "https://medwiki.toolforge.org/get_html.php";
+  var cxServerApiURL = "/get_html.php";
+
+  // If revision is requested, load that revision of page.
+  if (revision) {
+    cxServerParams.revision = revision;
+  } else {
+    cxServerParams.title = title;
+  }
+  const options = {
+    method: 'GET',
+    dataType: 'json'
+  }
+  const searchParams = new URLSearchParams(cxServerParams);
+
+  cxServerApiURL = `${cxServerApiURL}?${searchParams.toString()}`;
+
+  const result = fetch(cxServerApiURL, options)
+    .then((response) => response.json())
+    .then((response) => response.segmentedContent);
+
+  return result;
+};
path: app/src/wiki/mw/api/page.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**`fetchSegmentedContent_mdwiki` – unused vars, missing error handling, and non-standard `fetch` options**

* `targetWikiCode` is computed but never used.  
* `dataType: 'json'` is a jQuery option – it is silently ignored by the Fetch API.  
* A failed network request or non-2xx HTTP status will currently surface as an uncaught promise rejection when `response.json()` is called.  
* If the server responds with `{ error: … }`, the promise will still resolve and may break callers that expect `segmentedContent` to be present.

```diff
-  const targetWikiCode = siteMapper.getWikiDomainCode(targetLanguage);
…
-  const options = {
-    method: 'GET',
-    dataType: 'json'
-  }
+  const options = { method: 'GET' };
…
-  const result = fetch(cxServerApiURL, options)
-    .then((response) => response.json())
-    .then((response) => response.segmentedContent);
+  const result = fetch(cxServerApiURL, options)
+    .then((res) => {
+      if (!res.ok) {
+        throw new Error(`mdwiki fetch failed (${res.status})`);
+      }
+      return res.json();
+    })
+    .then((json) => {
+      if (!json.segmentedContent) {
+        throw new Error('mdwiki: missing segmentedContent in response');
+      }
+      return json.segmentedContent;
+    });
```

Adding basic validation makes the failure mode explicit and helps upstream callers (`fetchPageContent`) decide whether to fall back or propagate the error.  




> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -158,7 +167,97 @@ mw.cx.MachineTranslationService.prototype.fetchProvidersError = function () {
 	mw.log.error('[CX]', 'Unable to fetch machine translation providers.', arguments);
 };
 
+mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
+
+	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
+
+	// wait 2 seconds then do the next line
+
+	setTimeout(function () {
+		$('.cx-message-widget-message').append(
+			$('<a>')
+				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+				.attr('target', '_blank')
+				.text('Translation Dashboard')
+		);
+		$('.cx-message-widget-details').text(" Then refresh the page");
+
+	}, 2000)
+
+}
path: modules/mw.cx.MachineTranslationService.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`cxtoken_error` mixes service logic with DOM manipulation**

`MachineTranslationService` is a lower-level API helper; mutating `.cx-message-widget-*` elements here introduces a tight UI coupling and violates separation-of-concerns.

Refactor the method to **emit an event or reject the promise with a specific error code**, letting the UI layer decide how to render the login link.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 170-170: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 173-173: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 173-173: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 173-173: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 178-178: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: Strings must use singlequote.

(quotes)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 187-187: There must be a space before this paren.

(space-in-parens)

---

[error] 187-188: Missing semicolon.

(semi)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -158,7 +167,97 @@ mw.cx.MachineTranslationService.prototype.fetchProvidersError = function () {
 	mw.log.error('[CX]', 'Unable to fetch machine translation providers.', arguments);
 };
 
+mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
+
+	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
+
+	// wait 2 seconds then do the next line
+
+	setTimeout(function () {
+		$('.cx-message-widget-message').append(
+			$('<a>')
+				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+				.attr('target', '_blank')
+				.text('Translation Dashboard')
+		);
+		$('.cx-message-widget-details').text(" Then refresh the page");
+
+	}, 2000)
+
+}
+
+mw.cx.MachineTranslationService.prototype.fetchCXServerToken_issue = function () {
+	// cxtoken
+
+	if (this.sourceLanguage === "mdwiki") {
+		var params = {
+			user: mw.user.getName(),
+			wiki: this.targetLanguage,
+			ty: "cxtoken",
+		}
+		const options = {
+			method: 'GET',
+			dataType: 'json'
+		}
+
+		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
+
+		const result = fetch(url, options)
+			.then((response) => {
+				if (!response.ok) {
+					console.error('[CX] Error fetching mdwiki token:', response.status, response.statusText);
+					return response.json();
+				}
+				return response.json();
+			})
+			.then((data) => {
+				if (data.error && data.error.code === "no access") {
+					// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+					mw.cx.MachineTranslationService.prototype.cxtoken_error();
+					console.log('Fetch failed:', data);
+				} else {
+					console.log('Fetch successful:', data);
+				}
+			})
+			.catch(error => {
+				console.error('Error fetching mdwiki token:', error);
+			})
+		return result;
+	}
+
+	return new mw.Api().postWithToken('csrf', {
+		action: 'cxtoken',
+		assert: 'user'
+	});
+};
+
 mw.cx.MachineTranslationService.prototype.fetchCXServerToken = function () {
+	// cxtoken
+
+	if (this.sourceLanguage === "mdwiki") {
+		var params = {
+			user: mw.user.getName(),
+			wiki: this.targetLanguage,
+			ty: "cxtoken",
+		}
+		const options = {
+			method: 'GET',
+			dataType: 'json'
+		}
+
+		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
+
+		const result = fetch(url, options)
+			.then((response) => response.json())
+			.catch(error => {
+				console.error('Error fetching mdwiki token:', error);
+			});
+
+		return result;
+	}
+
path: modules/mw.cx.MachineTranslationService.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`fetchCXServerToken` branch for `"mdwiki"` may break `getCXServerToken()` expectations**

`getCXServerToken()` assumes the resolved object contains at least `{ age, jwt }`.  
The new fetch returns whatever `token.php` sends, but:

* There is no validation or defaulting – `token.age` will be `undefined`, leading to `NaN` arithmetic (`now + token.age`).
* On network failure the function resolves `undefined`, causing a crash downstream.

Suggestion:

```diff
return fetch(url, { method: 'GET' })
-  .then((response) => response.json())
+  .then((res) => {
+      if (!res.ok) {
+          throw new Error(`mdwiki token fetch failed: ${res.status}`);
+      }
+      return res.json();
+  })
+  .then((token) => {
+      if (!token || typeof token.age !== 'number' || !token.jwt) {
+          throw new Error('Invalid token format from mdwiki');
+      }
+      return token;
+  })
```

Failing fast with a descriptive error lets the caller surface a meaningful message instead of an opaque TypeError later in the promise chain.  



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 239-239: There must be a space after this paren.

(space-in-parens)

---

[error] 239-239: Strings must use singlequote.

(quotes)

---

[error] 239-239: There must be a space before this paren.

(space-in-parens)

---

[error] 240-244: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Strings must use singlequote.

(quotes)

---

[error] 243-243: Unexpected trailing comma.

(comma-dangle)

---

[error] 244-245: Missing semicolon.

(semi)

---

[error] 248-249: Missing semicolon.

(semi)

---

[error] 250-250: Unexpected var, use let or const instead.

(no-var)

---

[error] 250-250: Strings must use singlequote.

(quotes)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

---

[error] 252-252: There must be a space after this paren.

(space-in-parens)

---

[error] 252-252: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space after this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 253-253: There must be a space before this paren.

(space-in-parens)

---

[error] 254-254: There must be a space after this paren.

(space-in-parens)

---

[error] 254-254: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 255-255: Unexpected console statement.

(no-console)

---

[error] 255-255: There must be a space after this paren.

(space-in-parens)

---

[error] 255-255: There must be a space before this paren.

(space-in-parens)

---

[error] 256-256: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -372,6 +393,12 @@ mw.cx.init.Translation.prototype.fetchDraftTranslation = function (
  * @return {Promise<mw.cx.dm.DraftTranslationDTO|null>} Draft or null.
  */
 mw.cx.init.Translation.prototype.fetchDraftTranslationSuccess = function (draft, conflict) {
+	const admin_users = ['Doc James', 'Mr. Ibrahem 1'];
+	if (admin_users.includes(mw.user.getName())) {
+		console.log('[CX] fetchDraftTranslationSuccess. admin_users');
+		// Stop further processing
+		return Promise.resolve(null);
+	}
path: modules/mw.cx.init.Translation.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Hard-coded user white-list is fragile**

Embedding personal usernames in source code:

```js
const admin_users = ['Doc James', 'Mr. Ibrahem 1'];
```

• breaks as soon as a name changes,  
• is untestable, and  
• ignores MediaWiki’s permission model (`sysop`, `translationadmin`, …).

Use a proper rights check instead:

```js
if ( mw.user.isAllowed( 'contenttranslation-admin' ) ) { … }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 396-396: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 396-396: A space is required after '['.

(array-bracket-spacing)

---

[error] 396-396: A space is required before ']'.

(array-bracket-spacing)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: Identifier 'admin_users' is not in camel case.

(camelcase)

---

[error] 397-397: ES2016 'Array.prototype.includes' method is forbidden.

(es-x/no-array-prototype-includes)

---

[error] 397-397: There must be a space after this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 397-397: There must be a space before this paren.

(space-in-parens)

---

[error] 398-398: Unexpected console statement.

(no-console)

---

[error] 398-398: There must be a space after this paren.

(space-in-parens)

---

[error] 398-398: There must be a space before this paren.

(space-in-parens)

---

[error] 400-400: There must be a space after this paren.

(space-in-parens)

---

[error] 400-400: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:32Z
author_association: NONE
diff_hunk: @@ -85,6 +85,18 @@ mw.cx.init.Translation.prototype.init = function () {
 
 			this.sourceWikiPage.setRevision(sourcePageContent.revision);
 
+			if (this.sourceWikiPage.getLanguage() == "mdwiki") {
+				var segmented_error = sourcePageContent.error;
+				if (segmented_error) {
+					this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), segmented_error);
+					// return;
+				} else if (sourcePageContent.segmentedContent.length === 0) {
+					// if length of segmentedContent is 0, it means the source page is empty print error message
+					this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), 'Source text is empty refresh the page.');
+					// return;
+				}
+			}
path: modules/mw.cx.init.Translation.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Stop processing after fatal “mdwiki” fetch errors & clean‐up style**

• The function displays a critical error but still falls through to `initTranslationModel`, which will subsequently access `sourcePageContent.segmentedContent` and throw if the server returned an error or an empty array.  
• Non-strict comparison (`==`) and snake-case + `var` break the project ESLint rules.

```diff
- if (this.sourceWikiPage.getLanguage() == "mdwiki") {
-     var segmented_error = sourcePageContent.error;
-     if (segmented_error) {
-         this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), segmented_error);
-         // return;
-     } else if (sourcePageContent.segmentedContent.length === 0) {
-         this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), 'Source text is empty refresh the page.');
-         // return;
-     }
- }
+ if (this.sourceWikiPage.getLanguage() === 'mdwiki') {
+     const segmentedError = sourcePageContent.error;
+     if (segmentedError) {
+         this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), segmentedError);
+         return Promise.reject(new Error(segmentedError)); // abort init
+     }
+     if (!Array.isArray(sourcePageContent.segmentedContent) || sourcePageContent.segmentedContent.length === 0) {
+         this.translationView.showMessage(
+             'error',
+             mw.msg('cx-init-critical-error'),
+             'Source text is empty – please refresh the page.'
+         );
+         return Promise.reject(new Error('Empty mdwiki source text'));
+     }
+ }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
			if (this.sourceWikiPage.getLanguage() === 'mdwiki') {
			    const segmentedError = sourcePageContent.error;
			    if (segmentedError) {
			        this.translationView.showMessage('error', mw.msg('cx-init-critical-error'), segmentedError);
			        return Promise.reject(new Error(segmentedError)); // abort init
			    }
			    if (!Array.isArray(sourcePageContent.segmentedContent) || sourcePageContent.segmentedContent.length === 0) {
			        this.translationView.showMessage(
			            'error',
			            mw.msg('cx-init-critical-error'),
			            'Source text is empty – please refresh the page.'
			        );
			        return Promise.reject(new Error('Empty mdwiki source text'));
			    }
			}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 88-88: There must be a space after this paren.

(space-in-parens)

---

[error] 88-88: Expected '===' and instead saw '=='.

(eqeqeq)

---

[error] 88-88: Strings must use singlequote.

(quotes)

---

[error] 88-88: There must be a space before this paren.

(space-in-parens)

---

[error] 89-89: Unexpected var, use let or const instead.

(no-var)

---

[error] 89-89: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space after this paren.

(space-in-parens)

---

[error] 90-90: Identifier 'segmented_error' is not in camel case.

(camelcase)

---

[error] 90-90: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space after this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 91-91: There must be a space before this paren.

(space-in-parens)

---

[error] 93-93: There must be a space after this paren.

(space-in-parens)

---

[error] 93-93: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space after this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

---

[error] 95-95: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -258,7 +270,16 @@ mw.cx.init.Translation.prototype.attachToDOM = function (veTarget) {
  * @param {mw.cx.SiteMapper} siteMapper
  * @return {Promise}
  */
+
 mw.cx.init.Translation.prototype.fetchSourcePageContent = function (wikiPage, targetLanguage, siteMapper) {
+
+	if (wikiPage.getLanguage() === "mdwiki") {
+		let page_title = wikiPage.getTitle();
+		let user_name = mw.user.getName();
+		let tr_type = this.config.tr_type;
+		return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(page_title, targetLanguage, tr_type, user_name);
+	}
path: modules/mw.cx.init.Translation.js
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Avoid leaking PII & adhere to code-style**

`user_name` is sent to an external endpoint. Exposing a real user name is PII and probably violates Wikimedia privacy policy unless the user has explicitly consented. Consider sending an opaque user token instead.

Additionally, prefer camel-case + `const`:

```diff
- let page_title = wikiPage.getTitle();
- let user_name = mw.user.getName();
- let tr_type = this.config.tr_type;
- return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(page_title, targetLanguage, tr_type, user_name);
+ const pageTitle = wikiPage.getTitle();
+ const translationType = this.config.tr_type;
+ return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
+     pageTitle,
+     targetLanguage,
+     translationType,
+     /* user */ null   // or another anonymous/non-PII identifier
+ );
```

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate and display the fetchSourcePageContent method around the reported lines
rg -n "fetchSourcePageContent" -C3 modules/mw.cx.init.Translation.js
```

Length of output: 1836

---



</details>

**Avoid leaking PII & adhere to code-style**

In `modules/mw.cx.init.Translation.js` (lines 274–281), you currently have:

```js
let page_title = wikiPage.getTitle();
let user_name  = mw.user.getName();   // PII leak
let tr_type    = this.config.tr_type;
return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
    page_title,
    targetLanguage,
    tr_type,
    user_name
);
```

Please make the following updates:

- Use `const` and camelCase for all locals (`pageTitle`, `translationType`).
- Do **not** send the raw user name (PII) to the external endpoint. Replace it with an opaque token or `null` unless the user has explicitly consented.
- Update the call signature to match the new argument.

Example refactor:

```diff
-mw.cx.init.Translation.prototype.fetchSourcePageContent = function (wikiPage, targetLanguage, siteMapper) {
-  if (wikiPage.getLanguage() === "mdwiki") {
-    let page_title = wikiPage.getTitle();
-    let user_name  = mw.user.getName();
-    let tr_type    = this.config.tr_type;
-    return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
-      page_title,
-      targetLanguage,
-      tr_type,
-      user_name
-    );
-  }
+mw.cx.init.Translation.prototype.fetchSourcePageContent = function (wikiPage, targetLanguage, siteMapper) {
+  if (wikiPage.getLanguage() === "mdwiki") {
+    const pageTitle       = wikiPage.getTitle();
+    const translationType = this.config.tr_type;
+    // TODO: replace with an opaque user token or null if no PII consent
+    const userToken       = null;
+
+    return mw.cx.TranslationMdwiki.fetchSourcePageContent_mdwiki(
+      pageTitle,
+      targetLanguage,
+      translationType,
+      userToken
+    );
+  }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 274-274: There must be a space after this paren.

(space-in-parens)

---

[error] 274-274: There must be a space before this paren.

(space-in-parens)

---

[error] 276-276: There must be a space after this paren.

(space-in-parens)

---

[error] 276-276: Strings must use singlequote.

(quotes)

---

[error] 276-276: There must be a space before this paren.

(space-in-parens)

---

[error] 277-277: Identifier 'page_title' is not in camel case.

(camelcase)

---

[error] 277-277: 'page_title' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 278-278: Identifier 'user_name' is not in camel case.

(camelcase)

---

[error] 278-278: 'user_name' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 279-279: Identifier 'tr_type' is not in camel case.

(camelcase)

---

[error] 279-279: 'tr_type' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 280-280: There must be a space after this paren.

(space-in-parens)

---

[error] 280-280: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -208,13 +214,46 @@ mw.cx.TargetArticle.prototype.publishSection = function () {
 mw.cx.TargetArticle.prototype.publishSuccess = function (response, jqXHR) {
 	const publishAction = this.translation.isSectionTranslation() ? 'cxpublishsection' : 'cxpublish';
 	const publishResult = response[publishAction];
+	console.log("publishResult:");
 
+	if (publishResult.save_result_all) {
+		console.log("_____");
+		console.log("local result: " + JSON.stringify(publishResult.save_result_all.result));
+		console.log("mdwiki_result: " + JSON.stringify(publishResult.save_result_all.mdwiki_result));
+	} else {
+		console.log(JSON.stringify(publishResult));
+	}
+	// {"result":"error","edit":{"error":"noaccess","username":"Mr. Ibrahem"}}
 	if (publishResult.result === 'success') {
-		this.translation.setTargetURL(publishResult.targeturl);
-		return this.publishComplete(publishResult.targettitle || null);
+		var targeturl = publishResult.targeturl;
+		if (this.sourceLanguage === "mdwiki" && publishResult.published_to != "local") {
+			targeturl = publishResult.targeturl_wiki;
+		}
+		var qid = "";
+		var wd_result = "";
+		if (publishResult.LinkToWikidata) {
+			qid = publishResult.LinkToWikidata.qid;
+			console.log('LinkToWikidata: ' + JSON.stringify(publishResult.LinkToWikidata));
+			// LinkToWikidata: {"result":"success","qid":"Q474070"}
+			wd_result = publishResult.LinkToWikidata.result;
+		}
+
+		this.translation.setTargetURL(targeturl);
+
+		var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+		// mdwiki_result: {"warnings":{"main":{"*":"Unrecognized parameters: wpCaptchaId, wpCaptchaWord."}},"edit":{"new":"","result":"Success","pageid":9895285,"title":"مستخدم:Mr. Ibrahem/أوبلتوكسيماب","contentmodel":"wikitext","oldrevid":0,"newrevid":69736856,"newtimestamp":"2025-03-02T00:36:55Z","watched":""},"LinkToWikidata":{"error":"Cannot create link for namespace:2","nserror":"","qid":"Q7876570"}}
+
+		var done = this.publishComplete(new_title || null);
path: modules/mw.cx.TargetArticle.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Potential crash when `save_result_all` is missing**

`publishResult.save_result_all.mdwiki_result.edit.title` is accessed unconditionally, but `save_result_all` is only present for mdwiki responses.

```diff
- var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+ const newTitle = OO.getProp( publishResult, 'save_result_all', 'mdwiki_result', 'edit', 'title' );
```

Guard with `OO.getProp` (safe accessor) or an explicit `if`.




> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 229-229: There must be a space after this paren.

(space-in-parens)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 229-229: Strings must use singlequote.

(quotes)

---

[error] 229-229: There must be a space before this paren.

(space-in-parens)

---

[error] 232-232: Unexpected var, use let or const instead.

(no-var)

---

[error] 232-232: Strings must use singlequote.

(quotes)

---

[error] 233-233: Unexpected var, use let or const instead.

(no-var)

---

[error] 233-233: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 233-233: Strings must use singlequote.

(quotes)

---

[error] 234-234: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: Unexpected console statement.

(no-console)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space after this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 236-236: There must be a space before this paren.

(space-in-parens)

---

[error] 238-238: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 241-241: There must be a space after this paren.

(space-in-parens)

---

[error] 241-241: There must be a space before this paren.

(space-in-parens)

---

[error] 243-243: Unexpected var, use let or const instead.

(no-var)

---

[error] 243-243: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: Unexpected var, use let or const instead.

(no-var)

---

[error] 246-246: There must be a space after this paren.

(space-in-parens)

---

[error] 246-246: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 246-246: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -619,3 +683,23 @@ mw.cx.TargetArticle.prototype.getTags = function (hasTooMuchUnmodifiedText) {
 
 	return tagString;
 };
+
+mw.cx.TargetArticle.prototype.addMdwikiLinks = function (targetLanguage, targetTitle, qid, wd_result) {
+	const pp = {
+		lang: targetLanguage,
+		title: targetTitle,
+		save: 1
+	};
+	var url = "https://mdwiki.toolforge.org/fixwikirefs.php?" + $.param(pp);
+	let link = `<a href='${url}' target='_blank'>Fix References</a>`
+
+	var wdlink = "";
+	if (qid != "" && qid != "undefined" && wd_result != "success") {
+		var target_wiki = targetLanguage + "wiki"
+		var wdurl = `https://www.wikidata.org/wiki/Special:SetSiteLink/${qid}/${target_wiki}?` + $.param({ page: targetTitle });
+		wdlink = ` - <a href='${wdurl}' target='_blank'>Link to Wikidata</a>`
+	}
+	var html = `<div style="float:left;">${link}${wdlink}</div>`;
+
+	$('.cx-message-widget-details').append(html);
+}
path: modules/mw.cx.TargetArticle.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**XSS risk – build DOM nodes instead of HTML string concatenation**

`targetTitle` may legally contain quotes or angle brackets. Injecting it via template literals can break attributes and allow XSS.

```diff
- var html = `<div style="float:left;">${link}${wdlink}</div>`;
- $('.cx-message-widget-details').append(html);
+ const $container = $( '<div>' ).css( 'float', 'left' );
+ $( '<a>' )
+   .attr( { href: url, target: '_blank' } )
+   .text( 'Fix References' )
+   .appendTo( $container );
+ if ( qid && wdResult !== 'success' ) {
+   $( '<span>' ).text( ' – ' ).appendTo( $container );
+   $( '<a>' )
+     .attr( { href: wdurl, target: '_blank' } )
+     .text( 'Link to Wikidata' )
+     .appendTo( $container );
+ }
+ $( '.cx-message-widget-details' ).append( $container );
```



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 687-687: There must be a space after this paren.

(space-in-parens)

---

[error] 687-687: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 687-687: There must be a space before this paren.

(space-in-parens)

---

[error] 693-693: Unexpected var, use let or const instead.

(no-var)

---

[error] 693-693: Strings must use singlequote.

(quotes)

---

[error] 693-693: There must be a space after this paren.

(space-in-parens)

---

[error] 693-693: There must be a space before this paren.

(space-in-parens)

---

[error] 694-694: 'link' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 694-694: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 694-694: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 694-695: Missing semicolon.

(semi)

---

[error] 696-696: Unexpected var, use let or const instead.

(no-var)

---

[error] 696-696: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space after this paren.

(space-in-parens)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: Identifier 'wd_result' is not in camel case.

(camelcase)

---

[error] 697-697: Expected '!==' and instead saw '!='.

(eqeqeq)

---

[error] 697-697: Strings must use singlequote.

(quotes)

---

[error] 697-697: There must be a space before this paren.

(space-in-parens)

---

[error] 698-698: Unexpected var, use let or const instead.

(no-var)

---

[error] 698-698: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 698-698: Strings must use singlequote.

(quotes)

---

[error] 698-699: Missing semicolon.

(semi)

---

[error] 699-699: Unexpected var, use let or const instead.

(no-var)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 699-699: Identifier 'target_wiki' is not in camel case.

(camelcase)

---

[error] 699-699: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 699-699: There must be a space after this paren.

(space-in-parens)

---

[error] 699-699: There must be a space before this paren.

(space-in-parens)

---

[error] 700-700: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 700-700: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 700-701: Missing semicolon.

(semi)

---

[error] 702-702: Unexpected var, use let or const instead.

(no-var)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) after '${'.

(template-curly-spacing)

---

[error] 702-702: Expected space(s) before '}'.

(template-curly-spacing)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

---

[error] 704-704: There must be a space after this paren.

(space-in-parens)

---

[error] 704-704: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -73,13 +98,35 @@ public function __construct(
 		$this->translationStore = $translationStore;
 		$this->targetUrlCreator = $targetUrlCreator;
 		$this->changeTagsStore = $changeTagsStore;
+		$this->published_to = "local";
 	}
 
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Dynamic property `$published_to` breaks on PHP 8.2**

Accessing undeclared properties is deprecated. Declare a typed property:

```php
/** @var string */
private string $publishedTo = 'local';
```

and replace `$this->published_to` with `$this->publishedTo`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -42,6 +42,31 @@
 use Wikimedia\ParamValidator\TypeDef\IntegerDef;
 use Wikimedia\Stats\IBufferingStatsdDataFactory;
 
+function post_to_target($params)
+{
+	// $url = 'https://mdwiki.toolforge.org/Translation_Dashboard/publish/main.php';
+	$url = 'https://mdwiki.toolforge.org/publish/main.php';
+	$ch = curl_init();
+
+	$usr_agent = "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)";
+
+	curl_setopt($ch, CURLOPT_URL, $url);
+	curl_setopt($ch, CURLOPT_POST, 1);
+	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+	// mdwiki_result: {"response":"Requests must have a user agent"}
+	curl_setopt($ch, CURLOPT_USERAGENT, $usr_agent);
+	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 15);
+
+	$response = curl_exec($ch);
+
+	curl_close($ch);
+	$js = json_decode($response, true) ?? ["response" => $response];
+	return $js;
+}
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Move `post_to_target` inside the class & enable TLS verification**

Placing a global namespaced function in a file that already defines a class pollutes the namespace and hampers autoloading.  
More importantly, `CURLOPT_SSL_VERIFYPEER` / `CURLOPT_SSL_VERIFYHOST` are not set, allowing silent MITM.

```diff
-function post_to_target( $params ) {
+private function postToTarget( array $params ) : array {
     …
-    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
+    curl_setopt_array( $ch, [
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_SSL_VERIFYPEER => true,
+        CURLOPT_SSL_VERIFYHOST => 2,
+    ] );
     …
 }
```

Then call `$this->postToTarget( … )`.




> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -93,19 +140,41 @@ protected function saveWikitext($title, $wikitext, $params)
 			$wikitext .= $categoryText;
 		}
 
+		$wikitext = trim($wikitext);
+
+		$sourceRevisionId = $this->translation->translation['sourceRevisionId'];
+
 		$sourceLink = '[[:' . Sitemapper::getDomainCode($params['from'])
 			. ':Special:Redirect/revision/'
-			. $this->translation->translation['sourceRevisionId']
-			. '|' . $params['sourcetitle'] . ']]';
+			. $sourceRevisionId
+			. '|' . $params['sourcetitle'] . ']] to:' . $params['to'] . " #mdwikicx";
 
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fatal error – wrong class name `Sitemapper`**

The imported class is `SiteMapper` (capital **M**). PHP class names are case-sensitive in namespaces.

```diff
- $sourceLink = '[[:' . Sitemapper::getDomainCode($params['from'])
+ $sourceLink = '[[:' . SiteMapper::getDomainCode($params['from'])
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
		$sourceLink = '[[:' . SiteMapper::getDomainCode($params['from'])
			. ':Special:Redirect/revision/'
			. $sourceRevisionId
			. '|' . $params['sourcetitle'] . ']] to:' . $params['to'] . " #mdwikicx";
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 3e0f62c to a3c4be9
created_at: 2025-05-10T02:03:33Z
author_association: NONE
diff_hunk: @@ -148,6 +151,12 @@ mw.cx.MachineTranslationService.prototype.fetchProviders = function () {
 		$to: this.targetLanguage
 	});
 
+	// {"mt":["MinT","Google"]}
+	// 2024 By: Ibrahem Qasim
+	if (this.targetLanguage === "or") {
+		return $.Deferred().resolve(["Google"]);
+	}
path: modules/mw.cx.MachineTranslationService.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Hard-coded provider list for `or` bypasses cxserver contract**

Returning `["Google"]` short-circuits server capability detection and may:

* Drift out of sync when cxserver adds/removes providers.
* Hide non-Google options for Oriya if they become available.

Prefer querying the server (and caching) or gate this behind a feature flag.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 156-156: There must be a space after this paren.

(space-in-parens)

---

[error] 156-156: Strings must use singlequote.

(quotes)

---

[error] 156-156: There must be a space before this paren.

(space-in-parens)

---

[error] 157-157: There must be a space after this paren.

(space-in-parens)

---

[error] 157-157: A space is required after '['.

(array-bracket-spacing)

---

[error] 157-157: Strings must use singlequote.

(quotes)

---

[error] 157-157: A space is required before ']'.

(array-bracket-spacing)

---

[error] 157-157: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:15:52Z
author_association: NONE
diff_hunk: @@ -158,7 +167,97 @@ mw.cx.MachineTranslationService.prototype.fetchProvidersError = function () {
 	mw.log.error('[CX]', 'Unable to fetch machine translation providers.', arguments);
 };
 
+mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
+
+	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
+
+	// wait 2 seconds then do the next line
+
+	setTimeout(function () {
+		$('.cx-message-widget-message').append(
+			$('<a>')
+				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+				.attr('target', '_blank')
+				.text('Translation Dashboard')
+		);
+		$('.cx-message-widget-details').text(" Then refresh the page");
+
+	}, 2000)
+
path: modules/mw.cx.MachineTranslationService.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`cxtoken_error` mixes service layer with DOM manipulation**

This utility class should not reach into `$('.cx-message-widget-*')`.  
Emit an event (e.g., `mw.hook('mw.cx.tokenExpired')`) or reject the promise with a specific error so UI code can render the message.

Separation-of-concerns keeps the service testable and avoids jQuery coupling.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 170-170: Identifier 'cxtoken_error' is not in camel case.

(camelcase)

---

[error] 173-173: Identifier 'html_text' is not in camel case.

(camelcase)

---

[error] 173-173: 'html_text' is never reassigned. Use 'const' instead.

(prefer-const)

---

[error] 173-173: 'html_text' is assigned a value but never used.

(no-unused-vars)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 174-174: There must be a space after this paren.

(space-in-parens)

---

[error] 174-174: There must be a space before this paren.

(space-in-parens)

---

[error] 178-178: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space after this paren.

(space-in-parens)

---

[error] 179-179: There must be a space before this paren.

(space-in-parens)

---

[error] 180-180: There must be a space after this paren.

(space-in-parens)

---

[error] 180-180: There must be a space before this paren.

(space-in-parens)

---

[error] 181-181: There must be a space after this paren.

(space-in-parens)

---

[error] 181-181: There must be a space before this paren.

(space-in-parens)

---

[error] 182-182: There must be a space after this paren.

(space-in-parens)

---

[error] 182-182: There must be a space before this paren.

(space-in-parens)

---

[error] 183-183: There must be a space after this paren.

(space-in-parens)

---

[error] 183-183: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 185-185: There must be a space after this paren.

(space-in-parens)

---

[error] 185-185: Strings must use singlequote.

(quotes)

---

[error] 185-185: There must be a space before this paren.

(space-in-parens)

---

[error] 187-187: There must be a space before this paren.

(space-in-parens)

---

[error] 187-188: Missing semicolon.

(semi)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:15:52Z
author_association: NONE
diff_hunk: @@ -158,7 +168,97 @@ mw.cx.MachineTranslationService.prototype.fetchProvidersError = function () {
 	mw.log.error('[CX]', 'Unable to fetch machine translation providers.', arguments);
 };
 
+mw.cx.MachineTranslationService.prototype.cxtoken_error = function () {
+
+	// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+	let html_text = "<a href='https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login' target='_blank'>Translation Dashboard</a>";
+	mw.hook('mw.cx.error').fire('OAuth session expired, Please Login again in ');
+
+	// wait 2 seconds then do the next line
+
+	setTimeout(function () {
+		$('.cx-message-widget-message').append(
+			$('<a>')
+				.attr('href', 'https://mdwiki.toolforge.org/Translation_Dashboard/auth.php?a=login')
+				.attr('target', '_blank')
+				.text('Translation Dashboard')
+		);
+		$('.cx-message-widget-details').text(" Then refresh the page");
+
+	}, 2000)
+
+}
+
+mw.cx.MachineTranslationService.prototype.fetchCXServerToken_issue = function () {
+	// cxtoken
+
+	if (this.sourceLanguage === "mdwiki") {
+		var params = {
+			user: mw.user.getName(),
+			wiki: this.targetLanguage,
+			ty: "cxtoken",
+		}
+		const options = {
+			method: 'GET',
+			headers: { 'Accept': 'application/json' }
+		}
+
+		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
+
+		const result = fetch(url, options)
+			.then((response) => {
+				if (!response.ok) {
+					console.error('[CX] Error fetching mdwiki token:', response.status, response.statusText);
+					return response.json();
+				}
+				return response.json();
+			})
+			.then((data) => {
+				if (data.error && data.error.code === "no access") {
+					// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
+					mw.cx.MachineTranslationService.prototype.cxtoken_error();
+					console.log('Fetch failed:', data);
+				} else {
+					console.log('Fetch successful:', data);
+				}
+			})
+			.catch(error => {
+				console.error('Error fetching mdwiki token:', error);
+			})
+		return result;
+	}
+
+	return new mw.Api().postWithToken('csrf', {
+		action: 'cxtoken',
+		assert: 'user'
+	});
+};
path: modules/mw.cx.MachineTranslationService.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove unused experimental method**

The `fetchCXServerToken_issue` method appears to be an experimental or unused version of token fetching. It's confusing to have multiple methods with similar names and purposes in the codebase.

If this is truly needed for debugging, it should be properly documented or moved to a development-only module.

```diff
-mw.cx.MachineTranslationService.prototype.fetchCXServerToken_issue = function () {
-	// cxtoken
-
-	if (this.sourceLanguage === "mdwiki") {
-		var params = {
-			user: mw.user.getName(),
-			wiki: this.targetLanguage,
-			ty: "cxtoken",
-		}
-		const options = {
-			method: 'GET',
-			headers: { 'Accept': 'application/json' }
-		}
-
-		var url = "https://mdwiki.toolforge.org/publish/token.php?" + $.param(params)
-
-		const result = fetch(url, options)
-			.then((response) => {
-				if (!response.ok) {
-					console.error('[CX] Error fetching mdwiki token:', response.status, response.statusText);
-					return response.json();
-				}
-				return response.json();
-			})
-			.then((data) => {
-				if (data.error && data.error.code === "no access") {
-					// mw.hook('mw.cx.error').fire('Unable to fetch cxtoken. !! ');
-					mw.cx.MachineTranslationService.prototype.cxtoken_error();
-					console.log('Fetch failed:', data);
-				} else {
-					console.log('Fetch successful:', data);
-				}
-			})
-			.catch(error => {
-				console.error('Error fetching mdwiki token:', error);
-			})
-		return result;
-	}
-
-	return new mw.Api().postWithToken('csrf', {
-		action: 'cxtoken',
-		assert: 'user'
-	});
-};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 192-192: Identifier 'fetchCXServerToken_issue' is not in camel case.

(camelcase)

---

[error] 195-195: There must be a space after this paren.

(space-in-parens)

---

[error] 195-195: Strings must use singlequote.

(quotes)

---

[error] 195-195: There must be a space before this paren.

(space-in-parens)

---

[error] 196-200: Unexpected var, use let or const instead.

(no-var)

---

[error] 199-199: Strings must use singlequote.

(quotes)

---

[error] 199-199: Unexpected trailing comma.

(comma-dangle)

---

[error] 200-201: Missing semicolon.

(semi)

---

[error] 203-203: Unnecessarily quoted property 'Accept' found.

(quote-props)

---

[error] 204-205: Missing semicolon.

(semi)

---

[error] 206-206: Unexpected var, use let or const instead.

(no-var)

---

[error] 206-206: Strings must use singlequote.

(quotes)

---

[error] 206-206: There must be a space after this paren.

(space-in-parens)

---

[error] 206-206: There must be a space before this paren.

(space-in-parens)

---

[error] 206-207: Missing semicolon.

(semi)

---

[error] 208-208: There must be a space after this paren.

(space-in-parens)

---

[error] 208-208: There must be a space before this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space after this paren.

(space-in-parens)

---

[error] 209-209: There must be a space before this paren.

(space-in-parens)

---

[error] 210-210: There must be a space after this paren.

(space-in-parens)

---

[error] 210-210: There must be a space before this paren.

(space-in-parens)

---

[error] 211-211: Unexpected console statement.

(no-console)

---

[error] 211-211: There must be a space after this paren.

(space-in-parens)

---

[error] 211-211: There must be a space before this paren.

(space-in-parens)

---

[error] 215-215: There must be a space before this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: There must be a space after this paren.

(space-in-parens)

---

[error] 216-216: There must be a space before this paren.

(space-in-parens)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 225-225: There must be a space after this paren.

(space-in-parens)

---

[error] 225-225: Expected parentheses around arrow function argument.

(arrow-parens)

---

[error] 226-226: Unexpected console statement.

(no-console)

---

[error] 226-226: There must be a space after this paren.

(space-in-parens)

---

[error] 226-226: There must be a space before this paren.

(space-in-parens)

---

[error] 227-227: There must be a space before this paren.

(space-in-parens)

---

[error] 227-228: Missing semicolon.

(semi)

---

[error] 231-231: There must be a space after this paren.

(space-in-parens)

---

[error] 234-234: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:06Z
author_association: NONE
diff_hunk: @@ -17,51 +17,51 @@ const defaultThumbnailSize = 120;
  * @returns {Promise<Page[]>}
  */
 const fetchPages = (language, titles) => {
-  const params = {
-    action: "query",
-    format: "json",
-    formatversion: 2,
-    prop: "info|pageprops|pageimages|description|pageviews|langlinkscount|revisions",
-    pvipdays: 7, // Last 7 days page views
-    piprop: "thumbnail|name|original",
-    rvprop: "size",
-    pithumbsize: defaultThumbnailSize,
-    titles: titles.join("|"),
-    origin: "*",
-    redirects: true,
-  };
-
-  const mwApi = siteMapper.getApi(language);
-
-  return mwApi.get(params).then((response) => {
-    const apiResponse = response.query.pages;
-    const redirects = response.query.redirects || [];
-    const redirectMap = redirects.reduce(
-      (rMap, redirect) => ({ ...rMap, [redirect.to]: redirect.from }),
-      {}
-    );
+    const params = {
+        action: "query",
+        format: "json",
+        formatversion: 2,
+        prop: "info|pageprops|pageimages|description|pageviews|langlinkscount|revisions",
+        pvipdays: 7, // Last 7 days page views
+        piprop: "thumbnail|name|original",
+        rvprop: "size",
+        pithumbsize: defaultThumbnailSize,
+        titles: titles.join("|"),
+        origin: "*",
+        redirects: true,
+    };
 
-    // consider title normalizations to also support non normalized titles for multi-word page titles
-    // e.g. "Greenhouse_gas" instead of "Greenhouse gas"
-    const titleNormalizations = response.query.normalized || [];
-    const normalizationMap = titleNormalizations.reduce(
-      (nMap, normalization) => ({
-        ...nMap,
-        [normalization.to]: normalization.from,
-      }),
-      {}
-    );
+    const mwApi = siteMapper.getApi(language);
+
+    return mwApi.get(params).then((response) => {
+        const apiResponse = response.query.pages;
+        const redirects = response.query.redirects || [];
+        const redirectMap = redirects.reduce(
+            (rMap, redirect) => ({ ...rMap, [redirect.to]: redirect.from }),
+            {}
+        );
path: app/src/wiki/mw/api/page.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use `push` instead of spread inside `reduce` to avoid O(n²) copies**

Both `redirectMap` and `normalizationMap` are built with `reduce` that clones the accumulator on every iteration (`{ ...rMap, … }`).  
On large title lists this degrades to quadratic time & memory.

```diff
-const redirectMap = redirects.reduce(
-    (rMap, redirect) => ({ ...rMap, [redirect.to]: redirect.from }),
-    {}
-);
+const redirectMap = redirects.reduce((rMap, redirect) => {
+    rMap[redirect.to] = redirect.from;
+    return rMap;
+}, {});
```

Do the same for `normalizationMap`.  
This preserves simplicity while scaling linearly.  



Also applies to: 48-52

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 40-40: Avoid the use of spread (`...`) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in `.reduce`) because it causes a time complexity of `O(n^2)`.
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,360 @@
+//
+const mdwiki_last_url = { url: "" };
+
+const ENDPOINTS = {
+	mdwiki: "https://mdwiki.org",
+	mdwiki_wmcloud: "https://mdwiki.wmcloud.org",
+	medwikiToolforge: "https://medwiki.toolforge.org",
+	local: "http://localhost:8000"
+}
+
+function get_last_url() {
+	return mdwiki_last_url.url;
+}
+
+function createCategoryObject(cat) {
+	console.log("createCategoryObject:", cat);
+	return {
+		adapted: true,
+		sourceTitle: `Category:${cat}`,
+		targetTitle: `Jamii:${cat}`
+	};
+}
+
+// Utility to detect Swahili categories in HTML
+async function add_sw_categories(html) {
+	const categories = [];
+	const regexInfoboxDrug = /infobox drug/i;
+	const regexInfoboxMedicalCondition = /infobox medical condition/i;
+
+	// if html has "infobox drug" categories.push( createCategoryObject("Madawa") );
+	// if html has "infobox medical condition" categories.push( createCategoryObject("Magonjwa") );
+
+	if (regexInfoboxDrug.test(html)) categories.push(createCategoryObject("Madawa"));
+
+	if (regexInfoboxMedicalCondition.test(html)) categories.push(createCategoryObject("Magonjwa"));
+
+	console.log(JSON.stringify(categories));
+
+	console.log("add_sw_categories. Done");
+
+	return categories;
+}
+
+async function fetchJson(url, options) {
+	mdwiki_last_url.url += url + "\n";
+	// ---
+	console.log("fetchJson:", url);
+	// ---
+	if (!options) {
+		options = {
+			method: "GET",
+			// mode: "cors", // السماح بطلب CORS
+			dataType: "json",
+		}
+	}
+	// ---
+	options.headers = {
+		"Content-Type": "application/json",
+		"User-Agent": "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)",
+		...(options.headers ?? {})
+	};
path: modules/mw.cx.TranslationMdwiki.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`User-Agent` header cannot be set from browsers**

Modern browsers forbid overriding the `User-Agent` request header; the fetch will be blocked with a
`Refused to set unsafe header "User-Agent"` error.

Remove the header or move the identifier to a safer header (e.g. `X-Tool-Name`).  

```diff
-    "User-Agent": "WikiProjectMed Translation Dashboard/1.0 (https://mdwiki.toolforge.org/; tools.mdwiki@toolforge.org)",
+    "X-Tool-Name": "WikiProjectMed Translation Dashboard/1.0",
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	options.headers = {
		"Content-Type": "application/json",
		"X-Tool-Name": "WikiProjectMed Translation Dashboard/1.0",
		...(options.headers ?? {})
	};
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:06Z
author_association: NONE
diff_hunk: @@ -8,12 +8,16 @@
  * @param {ve.init.mw.CXTarget} veTarget
  * @param {Object} config Translation configuration
  * @param {mw.cx.SiteMapper} config.siteMapper SiteMapper instance
+ * @param {string} config.campaign Campaign name for targeting purposes
  */
 mw.cx.TargetArticle = function MWCXTargetArticle(translation, veTarget, config) {
 	this.translation = translation;
 	this.veTarget = veTarget;
 	this.config = config;
 	this.siteMapper = config.siteMapper;
+	this.campaign = config.campaign || '';;
+
+	console.log('MWCXTargetArticle: campaign: ' + this.campaign);
path: modules/mw.cx.TargetArticle.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove console.log statement from production code**

Console logging statements should not be committed to production code. Consider using a proper logging mechanism if this information is needed for debugging.

```diff
-	console.log('MWCXTargetArticle: campaign: ' + this.campaign);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 20-20: Unexpected console statement.

(no-console)

---

[error] 20-20: There must be a space after this paren.

(space-in-parens)

---

[error] 20-20: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:06Z
author_association: NONE
diff_hunk: @@ -208,13 +214,46 @@ mw.cx.TargetArticle.prototype.publishSection = function () {
 mw.cx.TargetArticle.prototype.publishSuccess = function (response, jqXHR) {
 	const publishAction = this.translation.isSectionTranslation() ? 'cxpublishsection' : 'cxpublish';
 	const publishResult = response[publishAction];
+	console.log("publishResult:");
 
+	if (publishResult.save_result_all) {
+		console.log("_____");
+		console.log("local result: " + JSON.stringify(publishResult.save_result_all.result));
+		console.log("mdwiki_result: " + JSON.stringify(publishResult.save_result_all.mdwiki_result));
+	} else {
+		console.log(JSON.stringify(publishResult));
+	}
path: modules/mw.cx.TargetArticle.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove console.log statements and use proper error logging**

Multiple console.log statements should be replaced with proper logging mechanism. Additionally, use a structured approach to log the data rather than separate log statements.

```diff
-	console.log("publishResult:");
-
-	if (publishResult.save_result_all) {
-		console.log("_____");
-		console.log("local result: " + JSON.stringify(publishResult.save_result_all.result));
-		console.log("mdwiki_result: " + JSON.stringify(publishResult.save_result_all.mdwiki_result));
-	} else {
-		console.log(JSON.stringify(publishResult));
-	}
+	// If needed, use mw.log for debugging:
+	// mw.log.debug("Content Translation publish result:", publishResult);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	// If needed, use mw.log for debugging:
	// mw.log.debug( 'Content Translation publish result:', publishResult );
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 217-217: Unexpected console statement.

(no-console)

---

[error] 217-217: There must be a space after this paren.

(space-in-parens)

---

[error] 217-217: Strings must use singlequote.

(quotes)

---

[error] 217-217: There must be a space before this paren.

(space-in-parens)

---

[error] 219-219: There must be a space after this paren.

(space-in-parens)

---

[error] 219-219: There must be a space before this paren.

(space-in-parens)

---

[error] 220-220: Unexpected console statement.

(no-console)

---

[error] 220-220: There must be a space after this paren.

(space-in-parens)

---

[error] 220-220: Strings must use singlequote.

(quotes)

---

[error] 220-220: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: Unexpected console statement.

(no-console)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: Strings must use singlequote.

(quotes)

---

[error] 221-221: There must be a space after this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 221-221: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: Unexpected console statement.

(no-console)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: Strings must use singlequote.

(quotes)

---

[error] 222-222: There must be a space after this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 222-222: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: Unexpected console statement.

(no-console)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space after this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

---

[error] 224-224: There must be a space before this paren.

(space-in-parens)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -208,13 +214,46 @@ mw.cx.TargetArticle.prototype.publishSection = function () {
 mw.cx.TargetArticle.prototype.publishSuccess = function (response, jqXHR) {
 	const publishAction = this.translation.isSectionTranslation() ? 'cxpublishsection' : 'cxpublish';
 	const publishResult = response[publishAction];
+	console.log("publishResult:");
 
+	if (publishResult.save_result_all) {
+		console.log("_____");
+		console.log("local result: " + JSON.stringify(publishResult.save_result_all.result));
+		console.log("mdwiki_result: " + JSON.stringify(publishResult.save_result_all.mdwiki_result));
+	} else {
+		console.log(JSON.stringify(publishResult));
+	}
+	// {"result":"error","edit":{"error":"noaccess","username":"Mr. Ibrahem"}}
 	if (publishResult.result === 'success') {
-		this.translation.setTargetURL(publishResult.targeturl);
-		return this.publishComplete(publishResult.targettitle || null);
+		var targeturl = publishResult.targeturl;
+		if (this.sourceLanguage === "mdwiki" && publishResult.published_to != "local") {
+			targeturl = publishResult.targeturl_wiki;
+		}
+		var qid = "";
+		var wd_result = "";
+		if (publishResult.LinkToWikidata) {
+			qid = publishResult.LinkToWikidata.qid;
+			console.log('LinkToWikidata: ' + JSON.stringify(publishResult.LinkToWikidata));
+			// LinkToWikidata: {"result":"success","qid":"Q474070"}
+			wd_result = publishResult.LinkToWikidata.result;
+		}
+
+		this.translation.setTargetURL(targeturl);
+
+		var new_title = publishResult.save_result_all.mdwiki_result.edit.title;
+		// mdwiki_result: {"warnings":{"main":{"*":"Unrecognized parameters: wpCaptchaId, wpCaptchaWord."}},"edit":{"new":"","result":"Success","pageid":9895285,"title":"مستخدم:Mr. Ibrahem/أوبلتوكسيماب","contentmodel":"wikitext","oldrevid":0,"newrevid":69736856,"newtimestamp":"2025-03-02T00:36:55Z","watched":""},"LinkToWikidata":{"error":"Cannot create link for namespace:2","nserror":"","qid":"Q7876570"}}
+
+		var done = this.publishComplete(new_title || null);
+
+		if (this.sourceLanguage === "mdwiki") {
+			var title2 = new_title || this.getTargetTitle()
+			mw.cx.TargetArticle.prototype.addMdwikiLinks(this.targetLanguage, title2, qid, wd_result)
+		}
path: modules/mw.cx.TargetArticle.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Follow JavaScript best practices and consistent naming**

Use const/let instead of var, follow camelCase naming convention, and add missing semicolons.

```diff
-		if (this.sourceLanguage === "mdwiki") {
-			var title2 = new_title || this.getTargetTitle()
-			mw.cx.TargetArticle.prototype.addMdwikiLinks(this.targetLanguage, title2, qid, wd_result)
+		if (this.sourceLanguage === 'mdwiki') {
+			const title2 = newTitle || this.getTargetTitle();
+			this.addMdwikiLinks(this.targetLanguage, title2, qid, wdResult);
 		}
```

Also, note that `mw.cx.TargetArticle.prototype.addMdwikiLinks` should be called as `this.addMdwikiLinks` since you're already in an instance method.


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 248-248: There must be a space after this paren.

(space-in-parens)

---

[error] 248-248: Strings must use singlequote.

(quotes)

---

[error] 248-248: There must be a space before this paren.

(space-in-parens)

---

[error] 249-249: Unexpected var, use let or const instead.

(no-var)

---

[error] 249-249: Identifier 'new_title' is not in camel case.

(camelcase)

---

[error] 249-250: Missing semicolon.

(semi)

---

[error] 250-250: There must be a space after this paren.

(space-in-parens)

---

[error] 250-250: There must be a space before this paren.

(space-in-parens)

---

[error] 250-251: Missing semicolon.

(semi)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -263,30 +358,51 @@ public function publish()
 			);
 		}
 
-		$saveresult = $this->saveWikitext($targetTitle, $wikitext, $params);
-		$editStatus = $saveresult['edit']['result'];
+		$save_result_all = $this->saveWikitext($targetTitle, $wikitext, $params);
+
+		$saveresult = $save_result_all['result'] ?? [];
+		$saveresult_mdwiki = $save_result_all['mdwiki_result'] ?? [];
+
+		if ($params['from'] === "mdwiki") {
+			$saveresult = $saveresult_mdwiki;
+		};
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve variable naming and use proper PHP 7.4+ syntax**

Follow consistent naming conventions and use modern PHP syntax features.

```diff
-		$save_result_all = $this->saveWikitext($targetTitle, $wikitext, $params);
+		$saveResultAll = $this->saveWikitext($targetTitle, $wikitext, $params);

-		$saveresult = $save_result_all['result'] ?? [];
-		$saveresult_mdwiki = $save_result_all['mdwiki_result'] ?? [];
+		$saveResult = $saveResultAll['result'] ?? [];
+		$saveResultMdwiki = $saveResultAll['mdwiki_result'] ?? [];

-		if ($params['from'] === "mdwiki") {
-			$saveresult = $saveresult_mdwiki;
-		};
+		if ($params['from'] === 'mdwiki') {
+			$saveResult = $saveResultMdwiki;
+		}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -93,19 +161,41 @@ protected function saveWikitext($title, $wikitext, $params)
 			$wikitext .= $categoryText;
 		}
 
-		$sourceLink = '[[:' . Sitemapper::getDomainCode($params['from'])
+		$wikitext = trim($wikitext);
+
+		$sourceRevisionId = $this->translation->translation['sourceRevisionId'];
+
+		$sourceLink = '[[:' . SiteMapper::getDomainCode($params['from'])
 			. ':Special:Redirect/revision/'
-			. $this->translation->translation['sourceRevisionId']
-			. '|' . $params['sourcetitle'] . ']]';
+			. $sourceRevisionId
+			. '|' . $params['sourcetitle'] . ']] to:' . $params['to'] . " #mdwikicx";
 
 		$summary = $this->msg(
 			'cx-publish-summary',
 			$sourceLink
 		)->inContentLanguage()->text();
 
+		$user_name = $this->getUser()->getName();
+
+		if (isset($params['user']) && $params['user'] != '') {
+			$user_name = $params['user'];
+		}
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use strict comparison and better variable naming**

Replace loose comparison (`!=`) with strict comparison (`!==`) and follow camelCase naming convention.

```diff
-		$user_name = $this->getUser()->getName();
+		$userName = $this->getUser()->getName();

-		if (isset($params['user']) && $params['user'] != '') {
-			$user_name = $params['user'];
+		if (isset($params['user']) && $params['user'] !== '') {
+			$userName = $params['user'];
 		}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -122,8 +212,13 @@ protected function saveWikitext($title, $wikitext, $params)
 		);
 
 		$api->execute();
+		$result = $api->getResult()->getResultData();
+		// if ( $params['from'] === "mdwiki") return $mdwiki_result;
 
-		return $api->getResult()->getResultData();
+		return [
+			'result' => $result,
+			'mdwiki_result' => $mdwiki_result,
+		];
 	}
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use consistent variable naming and remove commented code**

Follow camelCase naming convention and remove commented code.

```diff
-		$result = $api->getResult()->getResultData();
-		// if ( $params['from'] === "mdwiki") return $mdwiki_result;
+		$result = $api->getResult()->getResultData();

 		return [
 			'result' => $result,
-			'mdwiki_result' => $mdwiki_result,
+			'mdwiki_result' => $mdwikiResult,
 		];
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
		$result = $api->getResult()->getResultData();

		return [
			'result'       => $result,
			'mdwiki_result' => $mdwikiResult,
		];
	}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -73,13 +119,35 @@ public function __construct(
 		$this->translationStore = $translationStore;
 		$this->targetUrlCreator = $targetUrlCreator;
 		$this->changeTagsStore = $changeTagsStore;
+		$this->published_to = "local";
 	}
 
 	protected function getParsoidClient(): ParsoidClient
 	{
 		return $this->parsoidClientFactory->createParsoidClient();
 	}
+	protected function publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name)
+	{
+		$t_Params = [
+			'title' => $title->getPrefixedDBkey(),
+			'revid' => $sourceRevisionId,
+			'text' => $wikitext,
+			'user' => $user_name,
+			'summary' => $summary,
+			'target' => $params['to'],
+			'campaign' => $params['campaign'] ?? '',
+			'sourcetitle' => $params['sourcetitle'],
+		];
+
+		// wpCaptchaId, wpCaptchaWord
+		if (isset($params['wpCaptchaId'])) {
+			$t_Params['wpCaptchaId'] = $params['wpCaptchaId'];
+			$t_Params['wpCaptchaWord'] = $params['wpCaptchaWord'];
+		}
 
+		$mdwiki_result = post_to_target($t_Params);
+		return $mdwiki_result;
+	}
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Follow PHP coding standards in the publishToMdwiki method**

The method should follow PSR coding standards with better variable naming and type hints.

```diff
-	protected function publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name)
+	/**
+	 * Publish content to mdwiki external service
+	 *
+	 * @param Title $title The target title
+	 * @param string $wikitext Content to publish
+	 * @param array $params Request parameters
+	 * @param int $sourceRevisionId Source revision ID
+	 * @param string $summary Edit summary
+	 * @param string $userName Username
+	 * @return array Response from mdwiki
+	 */
+	protected function publishToMdwiki(Title $title, string $wikitext, array $params, int $sourceRevisionId, string $summary, string $userName): array
 	{
-		$t_Params = [
+		$mdwikiParams = [
 			'title' => $title->getPrefixedDBkey(),
 			'revid' => $sourceRevisionId,
 			'text' => $wikitext,
-			'user' => $user_name,
+			'user' => $userName,
 			'summary' => $summary,
 			'target' => $params['to'],
 			'campaign' => $params['campaign'] ?? '',
 			'sourcetitle' => $params['sourcetitle'],
 		];

 		// wpCaptchaId, wpCaptchaWord
 		if (isset($params['wpCaptchaId'])) {
-			$t_Params['wpCaptchaId'] = $params['wpCaptchaId'];
-			$t_Params['wpCaptchaWord'] = $params['wpCaptchaWord'];
+			$mdwikiParams['wpCaptchaId'] = $params['wpCaptchaId'];
+			$mdwikiParams['wpCaptchaWord'] = $params['wpCaptchaWord'];
 		}

-		$mdwiki_result = post_to_target($t_Params);
-		return $mdwiki_result;
+		return $this->postToTarget($mdwikiParams);
 	}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	/**
	 * Publish content to mdwiki external service
	 *
	 * @param Title  $title            The target title
	 * @param string $wikitext        Content to publish
	 * @param array  $params           Request parameters
	 * @param int    $sourceRevisionId Source revision ID
	 * @param string $summary          Edit summary
	 * @param string $userName         Username
	 * @return array Response from mdwiki
	 */
	protected function publishToMdwiki(Title $title, string $wikitext, array $params, int $sourceRevisionId, string $summary, string $userName): array
	{
		$mdwikiParams = [
			'title'       => $title->getPrefixedDBkey(),
			'revid'       => $sourceRevisionId,
			'text'        => $wikitext,
			'user'        => $userName,
			'summary'     => $summary,
			'target'      => $params['to'],
			'campaign'    => $params['campaign'] ?? '',
			'sourcetitle' => $params['sourcetitle'],
		];

		// wpCaptchaId, wpCaptchaWord
		if (isset($params['wpCaptchaId'])) {
			$mdwikiParams['wpCaptchaId']   = $params['wpCaptchaId'];
			$mdwikiParams['wpCaptchaWord'] = $params['wpCaptchaWord'];
		}

		return $this->postToTarget($mdwikiParams);
	}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE
diff_hunk: @@ -93,19 +161,41 @@ protected function saveWikitext($title, $wikitext, $params)
 			$wikitext .= $categoryText;
 		}
 
-		$sourceLink = '[[:' . Sitemapper::getDomainCode($params['from'])
+		$wikitext = trim($wikitext);
+
+		$sourceRevisionId = $this->translation->translation['sourceRevisionId'];
+
+		$sourceLink = '[[:' . SiteMapper::getDomainCode($params['from'])
 			. ':Special:Redirect/revision/'
-			. $this->translation->translation['sourceRevisionId']
-			. '|' . $params['sourcetitle'] . ']]';
+			. $sourceRevisionId
+			. '|' . $params['sourcetitle'] . ']] to:' . $params['to'] . " #mdwikicx";
 
 		$summary = $this->msg(
 			'cx-publish-summary',
 			$sourceLink
 		)->inContentLanguage()->text();
 
+		$user_name = $this->getUser()->getName();
+
+		if (isset($params['user']) && $params['user'] != '') {
+			$user_name = $params['user'];
+		}
+
+		$mdwiki_result = false;
+
+		if ($params['from'] === "mdwiki") { #$mdwiki_result
+
+			$mdwiki_result = $this->publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name);
+			$this->published_to = "mdwiki";
+			// return $Result;
+
+			$wikitext = "<pre>$wikitext</pre>";
+			$wikitext .= "\n{{tr|" . $params['to'] . '|' . $params['sourcetitle'] . '|' . $user_name . '}}';
+		}
path: includes/ActionApi/ApiContentTranslationPublish.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add proper validation and consistent string usage**

Use single quotes for PHP strings when not doing variable interpolation, add string validation, and follow consistent formatting.

```diff
-		$mdwiki_result = false;
+		$mdwikiResult = false;

-		if ($params['from'] === "mdwiki") { #$mdwiki_result
+		if ($params['from'] === 'mdwiki') {
+			$mdwikiResult = $this->publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $userName);
+			$this->publishedTo = 'mdwiki';
-			$mdwiki_result = $this->publishToMdwiki($title, $wikitext, $params, $sourceRevisionId, $summary, $user_name);
-			$this->published_to = "mdwiki";
-			// return $Result;

 			$wikitext = "<pre>$wikitext</pre>";
 			$wikitext .= "\n{{tr|" . $params['to'] . '|' . $params['sourcetitle'] . '|' . $user_name . '}}';
 		}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        if ($params['from'] === 'mdwiki') {
            $mdwikiResult = $this->publishToMdwiki(
                $title,
                $wikitext,
                $params,
                $sourceRevisionId,
                $summary,
                $userName
            );
            $this->publishedTo = 'mdwiki';

            $wikitext = "<pre>$wikitext</pre>";
            $wikitext .= "\n{{tr|" . $params['to'] . '|' . $params['sourcetitle'] . '|' . $userName . '}}';
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T02:42:07Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-10T01:59:50Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce special handling for the "mdwiki" source language across backend and frontend components. New functions and logic branches are added to fetch, publish, and process translations involving "mdwiki", including integration with external APIs, campaign tracking, and user-specific behaviors. Several UI and API adjustments accommodate these workflows, including token fetching, error handling, and user interface enhancements.

## Changes

| File(s)                                                                                   | Change Summary |
|-------------------------------------------------------------------------------------------|----------------|
| app/src/wiki/mw/api/page.js                                                               | Added `fetchSegmentedContent_mdwiki` for "mdwiki" source; updated `fetchSegmentedContent` to delegate to it when appropriate; reformatted callbacks for clarity. |
| includes/ActionApi/ApiContentTranslationPublish.php                                       | Added global `post_to_target` function for external POST; added `publishToMdwiki` method; modified `saveWikitext` and `publish` methods to handle external publishing for "mdwiki", change wikitext formatting, use new title conventions, and return composite results; added optional `campaign` parameter. |
| modules/mw.cx.MachineTranslationManager.js<br>modules/mw.cx.MwApiRequestManager.js        | Added commented-out lines in constructors for potential "mdwiki" to "en" source language remapping (inactive, no runtime effect); added TODO comments referencing "mdwiki" work. |
| modules/mw.cx.MachineTranslationService.js                                                | Added special case for target language "or" to return fixed provider list; added methods `cxtoken_error` and `fetchCXServerToken_issue` for error handling and token fetching; modified `fetchCXServerToken` to fetch from custom URL for "mdwiki" source language. |
| modules/mw.cx.TargetArticle.js                                                            | Extended constructor to accept `campaign`; included `campaign` in publish API params; added detailed logging in `publishSuccess`; adjusted target URL and title handling for "mdwiki"; added `addMdwikiLinks` method to append UI links; improved captcha and OAuth session expiration error handling. |
| modules/mw.cx.TranslationController.js                                                    | Stored `config` as instance property; passed `campaign` option to `TargetArticle` during instantiation. |
| modules/mw.cx.init.Translation.js                                                         | Added "mdwiki" specific error handling after source content fetch; added specialized `fetchSourcePageContent_mdwiki` call; introduced early return bypass for specific admin users in draft translation success handling. |
| modules/mw.cx.init.js                                                                     | Added `campaign` and `tr_type` properties to `services` object from URL query parameters. |
| modules/source/mw.cx.SelectedSourcePage.js                                                | Changed language count to use explicit `langlinks` length; updated language decorator to hide "en" and "simple"; disabled filling source languages list; adjusted font weight styling for languages not in source titles. |
| modules/ui/mw.cx.ui.ArticleColumn.js                                                      | Passed `language` property to `PageTitleWidget` during initialization. |
| modules/base/mw.cx.SiteMapper.js                                                          | Simplified conditional assignments using ternary operators; improved URL and module path handling for "mdwiki"; restored dynamic sourceLanguages from API response. |
| modules/mw.cx.TranslationMdwiki.js                                                       | Added new module providing multiple async functions to fetch and process "mdwiki" and "medwiki" content via various APIs; includes HTML fetching, cleaning, segmentation, fallback logic, and category detection; exports main fetch functions and last URL getter. |
| modules/ui/widgets/mw.cx.ui.PageTitleWidget.js                                           | Renamed local variable `href2` to `wikipediaUrl` in `getExistingTitleWarning` method without logic change. |
| modules/ve-cx/init/ve.init.mw.CXTarget.js                                               | Renamed parameter `set_it` to `applyImmediately` in `prefetchTranslationForSection`; added null check before method call; reformatted loop spacing and added trailing newline in `onSurfaceReady`. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Frontend
    participant Backend
    participant MdwikiAPI

    User->>Frontend: Initiate translation (sourceLanguage = "mdwiki")
    Frontend->>Backend: fetchSegmentedContent_mdwiki()
    Backend->>MdwikiAPI: GET /get_html.php (with params)
    MdwikiAPI-->>Backend: segmentedContent
    Backend-->>Frontend: segmentedContent

    User->>Frontend: Publish translation
    Frontend->>Backend: publish (with campaign, etc.)
    Backend->>MdwikiAPI: POST /publish/main.php (with params)
    MdwikiAPI-->>Backend: mdwiki_result
    Backend-->>Frontend: Save result + mdwiki_result

    Frontend->>User: Show publish success, add mdwiki links if applicable
```

## Poem

> In the garden where code and carrots meet,  
> "mdwiki" hops in, a new friend to greet!  
> With tokens fetched and links anew,  
> Campaigns tracked, and titles too.  
> Rabbits cheer as features grow—  
> Publishing wide, with a bunny's glow!  
> 🥕✨

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>app/src/wiki/mw/api/page.js</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 8.57.0
> 
> ESLint couldn't find the config "@vue/prettier" to extend from. Please check that the name of the config is correct.
> 
> The config "@vue/prettier" was referenced from the config file in "/app/.eslintrc.js".
> 
> If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=mdwikicx/CX_REL1_43_05_2025&utm_content=2):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: MrIbrahem
created_at: 2025-05-10T02:12:56Z
body: @coderabbitai full review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-10T02:13:00Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Full review triggered.

</details>
author_association: NONE