Jump to content
Wikimedia Meta-Wiki

MediaWiki:Gadget-WishlistTranslation.js

From Meta, a Wikimedia project coordination wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
 // <nowiki>
 mw.loader.using(["vue","@wikimedia/codex","mediawiki.language.names","mediawiki.storage"]).then((require)=>{
 /**
  * WishlistTranslation: A gadget for machine translation of pages within the Community Wishlist.
  * From [[Community Tech]]
  * Compiled from source at https://gitlab.wikimedia.org/repos/commtech/wishlist-intake
  * Please submit code changes as a merge request to the source repository.
  */

 'use strict';



 function__$styleInject(css){
 if(!css)return;

 if(typeofwindow=='undefined')return;
 varstyle=document.createElement('style');
 style.setAttribute('media','screen');

 style.innerHTML=css;
 document.head.appendChild(style);
 returncss;
 }

 varvue=require('vue');
 varcodex=require('@wikimedia/codex');

 varwishHomePage="Community Wishlist";
 varwishIntakePage="Community Wishlist/Intake";
 varwishEditParam="editwish";
 varvoteRecordedStorageName="wishlist-intake-vote-added";
 varwishIndexTemplate="Community Wishlist/Wishes";
 varwishIndexTemplateAll="Community Wishlist/Wishes/All";
 varwishIndexTemplateRecent="Community Wishlist/Wishes/Recent";
 varwishIndexTemplateArchive="Community Wishlist/Wishes/Archive";
 varwishCategory="Community Wishlist/Wishes";
 varwishPagePrefix="Community Wishlist/Wishes/";
 varwishTemplate="Community Wishlist/Wish";
 varfocusAreaPagePrefix="Community Wishlist/Focus areas/";
 varfocusAreaVoteCountSuffix="/Vote count";
 varfocusAreaTemplate="Community Wishlist/Focus area";
 varfocusAreasTemplate="Community Wishlist/Focus areas";
 varfocusAreaTemplateAll="Community Wishlist/Focus areas/All";
 varfocusAreaTemplateTop="Community Wishlist/Focus areas/Top";
 varsupportTemplate="Community Wishlist/Support";
 varmessagesPage="MediaWiki:Gadget-WishlistIntake/messages";
 varinterfaceMessageGroupId="agg-Community_Wishlist_interface";
 varwishesMessageGroupId="agg-Community_Wishlist_wishes";
 varmaxRecentWishes=5;
 vargadgets={
 WishlistIntake:{
 description:"A gadget for the intake and editing of Community Wishlist proposals.",
 ResourceLoader:true,
 "default":true,
 hidden:true,
 "package":true,
 files:[
 "WishlistIntake.js"
 ],
 rights:[
 "editmyusercss"
 ],
 categories:[
 "Community Wishlist/Intake"
 ],
 dependencies:[
 "vue",
 "@wikimedia/codex",
 "mediawiki.util",
 "mediawiki.api",
 "user.options",
 "mediawiki.action.view.postEdit",
 "mediawiki.confirmCloseWindow",
 "mediawiki.jqueryMsg"
 ],
 peers:[
 "WishlistIntake-pagestyles"
 ]
 },
 "WishlistIntake-pagestyles":{
 peer:true,
 hidden:true,
 files:[
 "WishlistIntake-pagestyles.css"
 ],
 filesOnWiki:true
 },
 WishlistManager:{
 description:"A gadget helping with management tasks for the Community Wishlist.",
 ResourceLoader:true,
 "default":true,
 hidden:true,
 "package":true,
 files:[
 "WishlistManager.js"
 ],
 rights:[
 "editmyusercss"
 ],
 dependencies:[
 "mediawiki.api",
 "mediawiki.util"
 ]
 },
 WishlistTranslation:{
 description:"A gadget for machine translation of pages within the Community Wishlist.",
 ResourceLoader:true,
 "package":true,
 files:[
 "WishlistTranslation.js"
 ],
 categories:[
 "Community Wishlist/Intake"
 ],
 dependencies:[
 "vue",
 "@wikimedia/codex",
 "mediawiki.language.names",
 "mediawiki.storage"
 ],
 messages:[
 "communityrequests-translation-translatable",
 "communityrequests-translation-switch",
 "communityrequests-translation-progress",
 "communityrequests-translation-errors"
 ]
 }
 };
 varmessages={
 "communitywishlist-wish-loading-error":"There was an error while parsing the wish source text. It may contain invalid wikitext. Please [1ドル refresh] and try again, use the [[2ドル|source editor]], or ask for help on the [[3ドル|talk page]].",
 "communitywishlist-edit-with-form":"Edit with form",
 "communitywishlist-form-subtitle":"Welcome to the new Community Wishlist. Please fill in the form below to submit your wish.",
 "communitywishlist-form-error":"Something went wrong. Please try saving again, or ask for help on the [[1ドル|talk page]].",
 "communitywishlist-description":"Describe your problem",
 "communitywishlist-description-description":"Explain in detail the wish or problem you are addressing.",
 "communitywishlist-title":"Wish title",
 "communitywishlist-title-error":"Please enter a value for this field (between 1ドル and 2ドル {{PLURAL:2ドル|character|characters}}).",
 "communitywishlist-title-description":"Make sure your title contains a brief description of the wish or problem.",
 "communitywishlist-description-error":"Please enter a value for this field (1ドル or more {{PLURAL:1ドル|character|characters}}).",
 "communitywishlist-wishtype-label":"Which type best describes your wish?",
 "communitywishlist-wishtype-description":"For submitting a policy change request, please consult the applicable project.",
 "communitywishlist-wishtype-feature-label":"Feature request",
 "communitywishlist-wishtype-feature-description":"You want new features and functions that do not exist yet.",
 "communitywishlist-wishtype-bug-label":"Bug report",
 "communitywishlist-wishtype-bug-description":"You want a problem or error fixed with existing features.",
 "communitywishlist-wishtype-change-label":"System change",
 "communitywishlist-wishtype-change-description":"You want a currently working feature or function to be changed.",
 "communitywishlist-wishtype-unknown-label":"I'm not sure or I don't know",
 "communitywishlist-wishtype-unknown-description":"After receiving your wish, we will assign a relevant type.",
 "communitywishlist-wishtype-error":"Please select a wish type.",
 "communitywishlist-project-intro":"Which projects is your wish related to?",
 "communitywishlist-project-help":"Select all projects your wish will have an impact on.",
 "communitywishlist-project-all-projects":"All projects",
 "communitywishlist-project-show-less":"Show less",
 "communitywishlist-project-show-all":"Show all",
 "communitywishlist-project-other-label":"It's something else",
 "communitywishlist-project-other-description":"e.g. gadgets, bots and external tools",
 "communitywishlist-project-other-error":"Please enter a value for this field (greater than 1ドル {{PLURAL:1ドル|character|characters}}), or select a project checkbox.",
 "communitywishlist-project-no-selection":"Please select at least 1ドル {{PLURAL:1ドル|project checkbox|project checkboxes}}, or enter a value for the \"2ドル\" field.",
 "communitywishlist-audience-label":"Primary affected users",
 "communitywishlist-audience-description":"Describe which user group and situation this will affect the most",
 "communitywishlist-audience-error":"Please enter a value for this field (between 1ドル and 2ドル {{PLURAL:2ドル|characters}}).",
 "communitywishlist-phabricator-label":"Phabricator tasks (optional)",
 "communitywishlist-phabricator-desc":"Enter Phabricator task IDs or URLs.",
 "communitywishlist-phabricator-chip-desc":"A list of Phabricator task IDs.",
 "communitywishlist-create-success":"Your wish has been submitted.",
 "communitywishlist-edit-success":"Your wish has been saved.",
 "communitywishlist-view-all-wishes":"View all wishes.",
 "communitywishlist-close":"Close",
 "communitywishlist-publish":"Publish wish",
 "communitywishlist-save":"Save changes",
 "communitywishlist-support-focus-area":"Support focus area",
 "communitywishlist-support-focus-area-dialog-title":"Support \"1ドル\"",
 "communitywishlist-optional-comment":"Optional comment",
 "communitywishlist-supported":"Already supported",
 "communitywishlist-support-focus-area-confirmed":"You have voted in support of this focus area.",
 "communitywishlist-unsupport-focus-area":"Remove your support vote"
 };
 varimportedMessages=[
 "communityrequests-status-draft",
 "communityrequests-status-submitted",
 "communityrequests-status-open",
 "communityrequests-status-in-progress",
 "communityrequests-status-delivered",
 "communityrequests-status-blocked",
 "communityrequests-status-archived",
 "project-localized-name-commonswiki",
 "project-localized-name-group-wikinews",
 "project-localized-name-group-wikipedia",
 "project-localized-name-group-wikiquote",
 "project-localized-name-group-wikisource",
 "project-localized-name-group-wikiversity",
 "project-localized-name-group-wikivoyage",
 "project-localized-name-group-wiktionary",
 "project-localized-name-mediawikiwiki",
 "project-localized-name-metawiki",
 "project-localized-name-specieswiki",
 "project-localized-name-wikidatawiki",
 "project-localized-name-wikifunctionswiki",
 "wikimedia-otherprojects-cloudservices",
 "cancel",
 "wikimedia-copyrightwarning"
 ];
 varconfig={
 wishHomePage:wishHomePage,
 wishIntakePage:wishIntakePage,
 wishEditParam:wishEditParam,
 voteRecordedStorageName:voteRecordedStorageName,
 wishIndexTemplate:wishIndexTemplate,
 wishIndexTemplateAll:wishIndexTemplateAll,
 wishIndexTemplateRecent:wishIndexTemplateRecent,
 wishIndexTemplateArchive:wishIndexTemplateArchive,
 wishCategory:wishCategory,
 wishPagePrefix:wishPagePrefix,
 wishTemplate:wishTemplate,
 focusAreaPagePrefix:focusAreaPagePrefix,
 focusAreaVoteCountSuffix:focusAreaVoteCountSuffix,
 focusAreaTemplate:focusAreaTemplate,
 focusAreasTemplate:focusAreasTemplate,
 focusAreaTemplateAll:focusAreaTemplateAll,
 focusAreaTemplateTop:focusAreaTemplateTop,
 supportTemplate:supportTemplate,
 messagesPage:messagesPage,
 interfaceMessageGroupId:interfaceMessageGroupId,
 wishesMessageGroupId:wishesMessageGroupId,
 maxRecentWishes:maxRecentWishes,
 gadgets:gadgets,
 messages:messages,
 importedMessages:importedMessages
 };

 /**
  * Utility functions for the gadget
  */
 classWebUtil{

 /**
 	 * Get the full page name with underscores replaced by spaces.
 	 * We use this instead of wgTitle because it's possible to set up
 	 * the wishlist gadget for use outside the mainspace.
 	 *
 	 * @return {string}
 	 */
 staticgetPageName(){
 returnmw.config.get('wgPageName').replaceAll('_',' ');
 }

 /**
 	 * Is the current page a wish page?
 	 *
 	 * @return {boolean}
 	 */
 staticisWishPage(){
 returnthis.getPageName().startsWith(config.wishPagePrefix);
 }

 /**
 	 * Are we currently creating a new wish?
 	 *
 	 * @return {boolean}
 	 */
 staticisNewWish(){
 returnthis.getPageName().startsWith(config.wishIntakePage)&&
 mw.config.get('wgAction')==='view'&&
 !this.isWishEdit()&&
 // Don't load on diff pages
 !mw.config.get('wgDiffOldId');
 }

 /**
 	 * Are we currently viewing (but not editing) a wish page?
 	 *
 	 * @return {boolean}
 	 */
 staticisWishView(){
 returnthis.isWishPage()&&!this.isWishEdit()&&mw.config.get('wgAction')==='view';
 }

 /**
 	 * Are we currently editing a wish page?
 	 *
 	 * @return {boolean}
 	 */
 staticisWishEdit(){
 returnthis.isWishPage()&&!!mw.util.getParamValue(config.wishEditParam);
 }

 /**
 	 * Are we currently manually editing a wish page?
 	 *
 	 * @return {boolean}
 	 */
 staticisManualWishEdit(){
 returnthis.isWishPage()&&
 (
 mw.config.get('wgAction')==='edit'||
 document.documentElement.classList.contains('ve-active')
 );
 }

 /**
 	 * Are we currently on a focus area page?
 	 *
 	 * @return {boolean}
 	 */
 staticisFocusAreaPage(){
 returnthis.getPageName().startsWith(config.focusAreaPagePrefix);
 }

 /**
 	 * Get the user's preferred language.
 	 *
 	 * @return {string}
 	 */
 staticuserPreferredLang(){
 if(mw.config.get('wgArticleId')===0){
 // Use interface language for new pages.
 returnmw.config.get('wgUserLanguage');
 }
 // Use content language for existing pages.
 returnmw.config.get('wgContentLanguage');
 }

 /**
 	 * Is the user's preferred language right-to-left?
 	 *
 	 * @return {boolean}
 	 */
 staticisRtl(){
 return$('body').css('direction')==='rtl';
 }

 /**
 	 * Are we on a page related to creating, editing, or viewing a wish?
 	 * This can include viewing the revision history, manual editing of wishes, etc.
 	 *
 	 * @return {boolean}
 	 */
 staticisWishRelatedPage(){
 returnthis.isNewWish()||this.isWishEdit()||this.isWishView()||this.isWishPage();
 }

 /**
 	 * Should we show the intake form?
 	 *
 	 * @return {boolean}
 	 */
 staticshouldShowForm(){
 // Prevent form from loading on i.e. action=history
 returnmw.config.get('wgAction')==='view'&&
 (this.isNewWish()||this.isWishEdit());
 }

 /**
 	 * Get the slug for the wish derived from the page title.
 	 * This is the subpage title and not necessarily the wish title,
 	 * which is stored in the proposal content.
 	 *
 	 * @return {string|null} null if not a wish-related page
 	 */
 staticgetWishSlug(){
 if(this.isNewWish()){
 // New wishes have no slug yet.
 return'';
 }elseif(this.isWishPage()){
 // Existing wishes have the page prefix stripped.
 constslugPortion=this.getPageName().slice(config.wishPagePrefix.length);
 // Strip off language subpage. Slashes are disallowed in wish slugs.
 returnslugPortion.split('/')[0];
 }
 returnnull;
 }

 /**
 	 * Get the slug for the focus area derived from the page title.
 	 *
 	 * @return {string|null} null if not a focus area page
 	 */
 staticgetFocusAreaSlug(){
 if(this.isFocusAreaPage()){
 constslugPortion=this.getPageName().slice(config.focusAreaPagePrefix.length);
 // Strip off language subpage. Slashes are disallowed in focus area slugs.
 returnslugPortion.split('/')[0];
 }
 returnnull;
 }

 /**
 	 * Get the full page title of the wish from the slug.
 	 *
 	 * @param {string} slug
 	 * @return {string}
 	 */
 staticgetWishPageTitleFromSlug(slug){
 returnconfig.wishPagePrefix+slug;
 }

 /**
 	 * Is the user WMF staff?
 	 *
 	 * @todo WMF-specific
 	 * @return {boolean}
 	 */
 staticisStaff(){
 return/\s\(WMF\)$|-WMF$/.test(mw.config.get('wgUserName'));
 }

 /**
 	 * Log an error to the console.
 	 *
 	 * @param {string} text
 	 * @param {Error} error
 	 */
 staticlogError(text,error){
 mw.log.error(`[WishlistIntake] ${text}`,error);
 }

 /**
 	 * Get a CSS-only Codex Message component of the specified type.
 	 * This is for use outside the Vue application.
 	 *
 	 * @param {mw.Message} message
 	 * @param {string} type 'notice', 'warning', 'error' or 'success'
 	 * @return {HTMLDivElement}
 	 */
 staticgetMessageBox(message,type){
 constmessageBlock=document.createElement('div');
 // The following messages may be used here:
 // * cdx-message--notice
 // * cdx-message--warning
 // * cdx-message--error
 // * cdx-message--success
 messageBlock.classList.add('cdx-message','cdx-message--block',`cdx-message--${type}`);
 if(type==='warning'){
 messageBlock.role='alert';
 }else{
 messageBlock.ariaLive='polite';
 }
 consticon=document.createElement('span');
 icon.classList.add('cdx-message__icon');
 constcontent=document.createElement('div');
 content.classList.add('cdx-message__content');
 content.innerHTML=message.parse();
 messageBlock.appendChild(icon);
 messageBlock.appendChild(content);
 returnmessageBlock;
 }

 /**
 	 * Is the user viewing in mobile format?
 	 *
 	 * @return {boolean}
 	 */
 staticisMobile(){
 return!!mw.config.get('wgMFMode');
 }

 /**
 	 * Fetch messages from the wiki and set them in mw.messages.
 	 *
 	 * @param {mw.Api} api
 	 * @return {jQuery.Promise}
 	 */
 staticsetOnWikiMessages(api){
 consttitles=[config.messagesPage+'/en'],
 langPageLocal=config.messagesPage+'/'+mw.config.get('wgUserLanguage');

 if(mw.config.get('wgUserLanguage')!=='en'){
 titles.push(langPageLocal);
 }

 returnapi.get({
 action:'query',
 prop:'revisions',
 titles,
 rvprop:'content',
 rvslots:'main',
 format:'json',
 formatversion:2,
 // Cache for 30 minutes.
 maxage:1800,
 smaxage:1800
 }).then((resp)=>{
 letmessagesLocal={},
 messagesEn={};
 /**
 			 * The content model of the messages page is wikitext so that it can be used with
 			 * Extension:Translate. Consequently, it's possible to break things. This just does
 			 * a try/catch and returns the default English messages if it fails.
 			 *
 			 * @param {string} title
 			 * @param {string} content
 			 * @return {Object}
 			 */
 constparseJSON=(title,content)=>{
 try{
 returnJSON.parse(content);
 }catch(e){
 WebUtil.logError(`Failed to parse JSON for ${title}.`,e);
 return{messages:config.messages};
 }
 };
 resp.query.pages.forEach((page)=>{
 if(!page.revisions){
 // Missing
 return;
 }
 constpageObj=page.revisions[0].slots.main;
 constparsedContent=parseJSON(config.messagesPage,pageObj.content);

 if(page.title===langPageLocal&&mw.config.get('wgUserLanguage')!=='en'){
 messagesLocal=parsedContent.messages;
 }else{
 messagesEn=parsedContent.messages;
 }
 });

 mw.messages.set(Object.assign(messagesEn,messagesLocal));
 });
 }
 }

 varscript=vue.defineComponent({
 name:'WishlistTranslationBanner',
 components:{
 CdxMessage:codex.CdxMessage,
 CdxProgressBar:codex.CdxProgressBar,
 CdxToggleSwitch:codex.CdxToggleSwitch
 },
 props:{
 translatableNodes:{type:Array,default:()=>[]},
 targetLang:{type:String,default:''},
 targetLangDir:{type:String,default:'ltr'}
 },
 setup(){
 // eslint-disable-next-line n/no-missing-require
 conststorage=require('mediawiki.storage').local;
 conststorageName='wishlist-intake-translation-enabled';
 // @todo Load these from Codex. T311099.
 constcdxIconRobot='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><!----><g><path d="M10.5 5h6.505C18.107 5 19 5.896 19 6.997V14h-7v2h5.005c1.102 0 1.995.888 1.995 2v2H1v-2c0-1.105.893-2 1.995-2H8v-2H1V6.997C1 5.894 1.893 5 2.995 5H9.5V2.915a1.5 1.5 0 111 0zm-4 6a1.5 1.5 0 100-3 1.5 1.5 0 000 3m7 0a1.5 1.5 0 100-3 1.5 1.5 0 000 3"></path></g></svg>';
 return{
 storage,
 storageName,
 cdxIconRobot
 };
 },
 data(){
 return{
 enabled:this.storage.get(this.storageName)==='1',
 inprogress:0,
 translatedNodeCount:0,
 errors:[]
 };
 },
 computed:{
 translatableNodeCount(){
 returnthis.translatableNodes.length;
 },
 userLanguageName(){
 constlangNames=mw.language.getData(this.targetLang,'languageNames');
 if(langNames&&langNames[this.targetLang]!==undefined){
 returnlangNames[this.targetLang];
 }
 returnthis.targetLang;
 }
 },
 methods:{
 onToggle(){
 this.storage.set(this.storageName,this.enabled?'1':'0');
 for(constnodeofthis.translatableNodes){
 if(!node.isConnected){
 // May have been removed since being queried in wishlistTranslation.init.js
 continue;
 }
 if(!this.enabled){
 // Disable by returning to untranslated values.
 node.nodeValue=node.nodeValueUntranslated;
 node.parentElement.lang=node.langOriginal;
 node.parentElement.dir=node.dirOriginal;
 continue;
 }else{
 if(node.nodeValueTranslated!==undefined){
 // If this node has been translated already, switch to that value.
 node.nodeValue=node.nodeValueTranslated;
 node.parentElement.lang=this.targetLang;
 node.parentElement.dir=this.targetLangDir;
 }else{
 // Otherwise, get the translation.
 node.nodeValueUntranslated=node.nodeValue;
 node.parentElement.style.opacity='0.6';
 this.inprogress++;
 // Note that node.lang has been set in the init script.
 this.getTranslation(node.nodeValueUntranslated,node.lang)
 .then((translatedHtml)=>{
 node.parentElement.style.opacity='';
 this.inprogress--;
 node.langOriginal=node.lang;
 node.dirOriginal=node.dir;
 if(translatedHtml===''){
 return;
 }
 node.parentElement.lang=this.targetLang;
 node.parentElement.dir=this.targetLangDir;
 this.translatedNodeCount++;
 node.nodeValueTranslated=translatedHtml;
 node.nodeValue=node.nodeValueTranslated;
 });
 }
 }
 }
 },
 /**
 		 * @param {string} html
 		 * @param {string} srcLang
 		 * @return {Promise<string>}
 		 */
 getTranslation(html,srcLang){
 consturl=`https://cxserver.wikimedia.org/v1/mt/${srcLang}/${this.targetLang}/MinT`;
 returnfetch(url,{
 method:'POST',
 headers:{
 Accept:'application/json',
 'Content-Type':'application/json'
 },
 body:JSON.stringify({html:html})
 }).then((response)=>{
 returnresponse.text().then((body)=>{
 // It is not always JSON that is returned. T373418.
 // @todo i18n for error messages
 letresponseBody='';
 try{
 responseBody=JSON.parse(body);
 }catch(e){
 this.errors.push('Unable to decode MinT response: '+body);
 return'';
 }
 if(!responseBody.contents){
 this.errors.push('No MinT response contents. Response was: '+body);
 return'';
 }
 // Wrap output with spaces if the input was (MinT strips them).
 return(html.startsWith(' ')?' ':'')+
 responseBody.contents+
 (html.endsWith(' ')?' ':'');
 });
 });
 }
 },
 mounted(){
 if(this.enabled){
 this.onToggle();
 }
 }
 });

 const_hoisted_1=["innerHTML"];
 const_hoisted_2=["innerHTML"];
 const_hoisted_3={key:0};
 const_hoisted_4={
 key:1,
 class:"wishlist-translation-errors"
 };

 functionrender(_ctx,_cache,$props,$setup,$data,$options){
 const_component_cdx_toggle_switch=vue.resolveComponent("cdx-toggle-switch");
 const_component_cdx_progress_bar=vue.resolveComponent("cdx-progress-bar");
 const_component_cdx_message=vue.resolveComponent("cdx-message");

 return(vue.openBlock(),vue.createElementBlock(vue.Fragment,null,[
 vue.createCommentVNode(" eslint-disable vue/no-v-html "),
 vue.createVNode(_component_cdx_message,{
 "allow-user-dismiss":"",
 icon:_ctx.cdxIconRobot
 },{
 default:vue.withCtx(()=>[
 vue.createElementVNode("div",{
 innerHTML:_ctx.$i18n(
 'communityrequests-translation-translatable',_ctx.userLanguageName
 ).parse()
 },null,8/* PROPS */,_hoisted_1),
 vue.createElementVNode("div",null,[
 vue.createVNode(_component_cdx_toggle_switch,{
 modelValue:_ctx.enabled,
 "onUpdate:modelValue":[
 _cache[0]||(_cache[0]=$event=>((_ctx.enabled)=$event)),
 _ctx.onToggle
 ]
 },{
 default:vue.withCtx(()=>[
 vue.createElementVNode("span",{
 innerHTML:_ctx.$i18n('communityrequests-translation-switch').parse()
 },null,8/* PROPS */,_hoisted_2)
 ]),
 _:1/* STABLE */
 },8/* PROPS */,["modelValue","onUpdate:modelValue"])
 ]),
 (_ctx.enabled&&_ctx.inprogress)
 ?(vue.openBlock(),vue.createElementBlock("div",_hoisted_3,[
 vue.createVNode(_component_cdx_progress_bar,{"aria-hidden":"true"}),
 vue.createTextVNode(" "+vue.toDisplayString(_ctx.$i18n('communityrequests-translation-progress')
 .params([_ctx.translatedNodeCount,_ctx.translatableNodeCount])
 .text()),1/* TEXT */)
 ]))
 :vue.createCommentVNode("v-if",true),
 (_ctx.enabled&&_ctx.errors.length>0)
 ?(vue.openBlock(),vue.createElementBlock("div",_hoisted_4,[
 vue.createElementVNode("strong",null,vue.toDisplayString(_ctx.$i18n('communityrequests-translation-errors').text()),1/* TEXT */),
 vue.createElementVNode("ul",null,[
 (vue.openBlock(true),vue.createElementBlock(vue.Fragment,null,vue.renderList(_ctx.errors,(error)=>{
 return(vue.openBlock(),vue.createElementBlock("li",{key:error},vue.toDisplayString(error),1/* TEXT */))
 }),128/* KEYED_FRAGMENT */))
 ])
 ]))
 :vue.createCommentVNode("v-if",true)
 ]),
 _:1/* STABLE */
 },8/* PROPS */,["icon"])
 ],2112/* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
 }

 __$styleInject(".wishlist-translation-errors {\n color: #d73333;\n}\n");

 script.render=render;

 /**
  * Entry point for the WishlistTranslation gadget.
  */
 if(WebUtil.getPageName().startsWith(config.wishHomePage)){
 mw.hook('wikipage.content').add(($content)=>{
 consttargetLang=mw.config.get('wgUserLanguage');
 getTranslatableNodes($content[0],targetLang).then((translatableNodes)=>{
 // Do nothing if there's nothing to translate.
 if(translatableNodes.length===0){
 return;
 }

 // Get the i18n messages, and mount the Vue app.
 constmessages=config.gadgets.WishlistTranslation.messages;
 (newmw.Api()).loadMessages(messages).then(()=>{
 constappRoot=document.createElement('div');
 $content[0].before(appRoot);
 constappData={
 targetLang:targetLang,
 // @todo Get the lang dir in a better way.
 targetLangDir:document.querySelector('html').dir,
 translatableNodes:translatableNodes
 };
 constVue=require('vue');
 Vue.createMwApp(script,appData).mount(appRoot);
 });
 });
 });
 }

 /**
  * Get all source languages supported by MinT for the given target language,
  * caching the result in localStorage for a day to avoid re-querying on every\
  * page load.
  *
  * @param {string} targetLang The target language code.
  * @return {Array<string,Array<string>>}
  */
 functiongetSupportedLangs(targetLang){
 // eslint-disable-next-line n/no-missing-require
 conststorage=require('mediawiki.storage').local;
 constlocalStorageKey='wishlist-intake-langlist-'+targetLang;
 conststored=storage.get(localStorageKey);
 if(stored){
 returnPromise.resolve(JSON.parse(stored));
 }
 consturl='https://cxserver.wikimedia.org/v1/list/mt';
 returnfetch(url).then((response)=>{
 returnresponse.text().then((body)=>{
 constsourceLangs=[];
 try{
 constmintLangs=JSON.parse(body).MinT;
 // The API maps each language to those that it can be translated to,
 // but we want a list of all possible source langs for our target.
 if(mintLangs[targetLang]){
 for(constsourceLangofObject.keys(mintLangs)){
 if(mintLangs[sourceLang].includes(targetLang)){
 sourceLangs.push(sourceLang);
 }
 }
 }
 }catch(e){
 // Unable to parse response.
 }
 // Store for 24 hours.
 storage.set(localStorageKey,JSON.stringify(sourceLangs),60*60*24);
 returnsourceLangs;
 });
 });
 }

 /**
  * Get all DOM nodes that need to be translated.
  *
  * @todo More needs to be done here to select nodes and/or elements that are
  * actually needing to be translated and that are of the most appropriate size
  * and scope. Probably we should be collecting elements and not leaf nodes, but
  * if we do that then in many cases we end up also having translations inside
  * them, so more work is needed there.
  *
  * @param {Element} content DOM containing at least one .mw-parser-output element.
  * @param {string} targetLang
  * @return {Promise<Array<Node>>}
  */
 functiongetTranslatableNodes(content,targetLang){
 constparserOutput=content.querySelector('.mw-parser-output');
 if(parserOutput===null){
 returnPromise.resolve([]);
 }

 returngetSupportedLangs(targetLang).then((supportedLangs)=>{
 // Find all text nodes that are in a different language to the interface language.
 constwalker=document.createTreeWalker(parserOutput,NodeFilter.SHOW_TEXT,(node)=>{
 // Skip empty nodes, and everything in the <languages /> bar.
 if(node.nodeValue.trim()===''||
 node.parentElement.closest('.mw-pt-languages')
 ){
 returnNodeFilter.FILTER_SKIP;
 }
 constlang=node.parentElement.closest('[lang]').lang;
 // Skip if they're the same language.
 if(lang===targetLang||
 // Skip style elements.
 node.parentElementinstanceofHTMLStyleElement||
 // Skip if any parent has `.translate-no`. T161486.
 // @todo Fix this to permit `.translate-yes` to be inside a `.translate-no`.
 node.parentElement.closest('.translate-no')
 ){
 returnNodeFilter.FILTER_SKIP;
 }
 // Check if the source lang can be translated to the target lang.
 if(!supportedLangs.includes(lang)){
 returnNodeFilter.FILTER_SKIP;
 }
 // Save the parent lang on the node for easier access when sending it for translation.
 node.lang=lang;
 returnNodeFilter.FILTER_ACCEPT;
 });

 // Get all nodes.
 letn=walker.nextNode();
 consttranslatableNodes=[];
 while(n){
 translatableNodes.push(n);
 n=walker.nextNode();
 }
 returntranslatableNodes;
 });
 }

 });
 // </nowiki>

AltStyle によって変換されたページ (->オリジナル) /