Jump to content

MediaWiki:FundraisingBanners/LocalizeJS-2022.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.
/* jshint maxerr: 600 */
/* == MediaWiki:FundraisingBanners/LocalizeJS-2022.js == */

/**
 * Get the currency for a given country
 *
 * NOTE: The following currency mapping is WMF-specific based on payment
 * provider availability, NOT necessarily the official currency of the country
 *
 * @param  {string} country code
 * @return {string} currency code
 */
frb.getCurrency = function(country) {
    switch ( country ) {
        // Big 6
        case 'US': return 'USD';
        case 'CA': return 'CAD';
        case 'AU': return 'AUD';
        case 'NZ': return 'NZD';
        case 'GB': return 'GBP';
        case 'IE': return 'EUR';
        // Euro countries
        case 'AT':
        case 'BE':
        case 'ES':
        case 'FR':
        case 'GR':
        case 'IE':
        case 'IT':
        case 'LU':
        case 'LV':
        case 'NL':
        case 'PT':
        case 'SK':
            return 'EUR';
        // Others
        case 'DK': return 'DKK';
        case 'HU': return 'HUF';
        case 'IL': return 'ILS';
        case 'IN': return 'INR';
        case 'JP': return 'JPY';
        case 'MY': return 'MYR';
        case 'NO': return 'NOK';
        case 'PL': return 'PLN';
        case 'CZ': return 'CZK';
        case 'RO': return 'RON';
        case 'SE': return 'SEK';
        case 'UA': return 'UAH';
        case 'ZA': return 'ZAR';
        // Latin America
        case 'BR': return 'BRL';
        case 'AR': return 'ARS';
        case 'CL': return 'CLP';
        case 'CO': return 'COP';
        case 'MX': return 'MXN';
        case 'PE': return 'PEN';
        case 'UY': return 'UYU';
        case 'CH': return 'CHF';
        case 'LI': return 'CHF';
        // Fall back to USD
        default:
            return 'USD';
    }
};

/**
 * Format a currency value
 *
 * TODO: make this handle the ISO code overrides?
 *
 * @param  {string} currency code. Leave undefined to get without symbol.
 * @param  {number} amount
 * @param  {string} language code
 * @return {string} formatted string e.g. '$3', '£5', '10 €'
 */
frb.formatCurrency = function(currency, amount, language) {

    var locale, formatterOptions, formatter, fmAmount, supportsIntl;

    if ( isNaN(amount) || amount === '' ) {
        // Not a number, it's probably the 'other' string or box
        // TODO: better way of doing this?
        fmAmount = amount;
    } else {
        // Check browser support
        try {
            supportsIntl = typeof window.Intl === 'object';
        } catch (e) {
            supportsIntl = false; // T265396
        }

        if ( supportsIntl ) {
            // Use Intl for fancy number formatting - thousands separators etc
            locale = language + '-' + mw.centralNotice.data.country;
            if ( amount % 1 !== 0 ) {
                formatterOptions = { minimumFractionDigits: 2 };
            } else {
                formatterOptions = {};
            }
            formatter = new Intl.NumberFormat(locale, formatterOptions);
        } else {
            // Bad browser i.e. IE. Just do the basics: 2 decimal places if needed, or none
            formatter = {};
            formatter.format = function(number) {
                if ( amount % 1 !== 0 ) {
                    return number.toFixed(2);
                } else {
                    return number.toString();
                }
            };
        }
        fmAmount = formatter.format(amount);
    }

    // No symbol needed
    if ( currency === undefined ) {
        return fmAmount;
    }

    // Better dive into the formatting object
    if ( frb.currencyFormats[currency] === undefined ) {
        return currency + ' ' + fmAmount;
    }
    if ( frb.currencyFormats[currency] instanceof Object ) { // not a string
        if ( frb.currencyFormats[currency][language] !== undefined ) {
            return frb.currencyFormats[currency][language].replace('\t', fmAmount);
        }
        return frb.currencyFormats[currency]['default'].replace('\t', fmAmount);
    }

    return frb.currencyFormats[currency].replace('\t', fmAmount);
};

/*
 * Select the correct amount or array of amounts from object in "source"
 *
 * @param {Object} source   - the amounts data object e.g. frb.amounts.options7, frb.amounts.averages
 * @param {string} currency - ISO code of currency
 * @param {string} country  - ISO code of country (optional)
 * @return {array/number}   - depending on source
 */
frb.pickAmounts = function(source, currency, country) {

    if ( source[currency]['default'] ) { // we need to go deeper
        if ( source[currency][country] !== undefined ) {
            return source[currency][country];
        } else {
            return source[currency]['default'];
        }
    } else {
        return source[currency];
    }
};

/* Credit card types so we can show the correct logos */
frb.cardTypes = {
    // Big 6
    'US' : 'vmad',
    'CA' : 'vma',
    'GB' : 'vmaj',
    'IE' : 'vmaj',
    'AU' : 'vmaj',
    'NZ' : 'vma',
    // Euro countries
    'AT' : 'vmaj',
    'BE' : 'vmaj',
    'ES' : 'vmaj',
    'FR' : 'vma',
    'GR' : 'vma',
    'IT' : 'vmaj',
    'LU' : 'vmaj',
    'LV' : 'vma',
    'NL' : 'vmaj',
    'PT' : 'vmaj',
    'SK' : 'vmaj',
    // Others
    'CZ' : 'vmad',
    'DK' : 'vma',
    'HU' : 'vma',
    'IL' : 'vmad',
    'JP' : 'vmaj',
    'MY' : 'vmaj',
    'NO' : 'vma',
    'PL' : 'vma',
    'RO' : 'vma',
    'SE' : 'vma',
    'UA' : 'vma',
    'ZA' : 'vm',
    'IN' : 'vmar', // dLocal - RuPay
    'CH' : 'vma',
    'LI' : 'vma'
};

/**
 * Should we show Apple Pay?
 *
 * Note there is a ~500ms delay in Safari when checking, so only call this if needed
 *
 * @param  {string} country
 * @return {boolean}
 */
frb.shouldShowApplePay = function ( country ) {
    // https://support.apple.com/en-us/HT207957 - minus China mainland
    var appleCountries = [
        'ZA',
        'AU', 'HK', 'JP', 'MO', 'NZ', 'SG', 'TW',
        'AM', 'AT', 'AZ', 'BY', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 
        'EE', 'FO', 'FI', 'FR', 'GE', 'DE', 'GR', 'GL', 'GG', 'HU', 
        'IS', 'IE', 'IM', 'IT', 'KZ', 'JE', 'LV', 'LI', 'LT', 'LU', 
        'MT', 'MC', 'ME', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM', 
        'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'UA', 'GB', 'VA', 
        'CO', 'CR', 'BR', 'MX', 
        'BH', 'IL', 'PS', 'QA', 'SA', 'AE', 
        'CA', 'US'
    ];
    if ( appleCountries.indexOf( country ) === -1 ) {
        return false;
    }
    if ( location.search.match('forceApplePay') ) {
        return true;
    }
    if ( window.ApplePaySession ) {
        if ( ApplePaySession.canMakePayments() ) {
            return true;
        }
    }
    return false;
};

/**
 * Display the correct payment methods for current country
 *
 * Methods should be labeled with class 'frb-pm-xxxx'
 * TODO: clean this function up more
 *
 * @param  {string} country
 */
frb.localizeMethods = function(country) {

    // Test country with *all the methods*
    if ( country === 'ZZ' ) {
        $('.frb-payment-options > div').show();
        return;
    }

    // Hide recurring completely for some countries and endowment
    if ( frb.isEndowment || frb.noRecurringCountries.indexOf(country) !== -1 ) {
        $('.frb-frequency, .recurring-details').hide();
    }

    // Remove any leftover WorldPay and Adyen
    $('.frb-pm-cc-wp').remove();
    $('.frb-pm-cc-adyen').remove();

    // Countries with no PayPal option
    var noPP = ['IN', 'RU', 'SG', 'AE', 'QA', 'OM', 'BD', 'BO', 'PA',
                'PY', 'GT', 'JM', 'TT', 'DZ'];
    if ($.inArray(country, noPP) !== -1) {
        $('.frb-pm-pp').remove();
        $('.frb-pm-pp-usd').remove();
    }

    // Countries with no PayPal for mobile only - https://phabricator.wikimedia.org/T173001
    var noPPmobile = ['PH', 'ID', 'TH', 'KR', 'MY', 'VN'];
    var mobileRegex = /(_mob_|_ipd_|_m_)/;
    if ($.inArray(country, noPPmobile) !== -1) {
        if (mw.centralNotice.data.banner.search(mobileRegex) !== -1) {
            $('.frb-pm-pp').remove();
            $('.frb-pm-pp-usd').remove();
        }
    }

    // Countries where PayPal must be in USD
    var ppUSD = ['BG', 'HR', 'LT', 'MK', 'RO', 'UA', 'SA', 'CN', 'ID', 'KR',
                 'KZ', 'MY', 'VN', 'AR', 'CL', 'DO', 'CO', 'NI', 'UY', 'ZA',
                 'BH', 'LB', 'VE', 'TR', 'IS', 'BA', 'MV', 'BB', 'BM', 'BZ',
                 'CR', 'CW', 'SX', 'HN', 'KN', 'DM', 'AG', 'LC', 'GD', 'FJ',
                 'TN', 'BJ', 'BF', 'CI', 'GW', 'ML', 'NE', 'SN', 'TG', 'BR',
                 'PE'];
    if ($.inArray(country, ppUSD) !== -1) {
        $('.frb-pm-pp').remove();
        $('.frb-pm-pp-usd').show();
    } else {
        $('.frb-pm-pp').show();
        $('.frb-pm-pp-usd').remove();
    }

    // Show any extra local payment methods, or remove them if not needed
    var extrapaymentmethods = {
        'amazon'   : ['US'], // Note Amazon was removed from current best 2023-10-20
        'bpay'     : [],
        'ideal'    : ['NL'],
        'bt'       : ['BR', 'AR', 'CO', 'CL', 'PE', 'MX', 'IN', 'ZA', 'CZ'], // Bank Transfer (dLocal/Adyen)
        'cash'     : ['MX', 'AR', 'CO', 'CL', 'PE', 'UY'],  // 'Cash' methods (dLocal)
        'pix'      : ['BR'],
        'boleto'   : ['BR']
    };

    // Methods with different labels per country

    var language = mw.config.get('wgUserLanguage');
    var btTranslation = 'Bank Transfer';

    if (language === 'pt') {

        if (country === 'BR') {
            btTranslation = 'Transferência bancária';
        }

    } else if (language === 'es') {

        if (country === 'CL') {
            btTranslation = 'WebPay';
        } else if (country === 'CO') {
            btTranslation = 'PSE Pagos';
        } else {
            btTranslation = 'Transferencia bancaria';
        }

    }

    if (country === 'CZ') {
        if (language === 'en') {
            btTranslation = 'Online Banking';
        }
        if (language === 'cs') {
            btTranslation = 'Internetové Bankovnictví';
        }
    }

    $( '.frb-pm-bt button, .frb-pm-bt label, button.frb-pm-bt' ).text( btTranslation );

    for (var method in extrapaymentmethods) {
        var $methodbutton = $('.frb-pm-' + method);
        if ( $.inArray(country, extrapaymentmethods[method]) !== -1 && !frb.isEndowment ) {
            $methodbutton.show();
        } else {
            $methodbutton.remove();
        }
    }

    // Google Pay - separated from extrapaymentmethods as we want to show on Endowment too
    var googlePayCountries = [
        'AE', 'AT', 'AR', 'AU', 'BE', 'BG', 'BR', 'CA', 'CH', 'CL', 'CO',
        'CZ', 'DE', 'DK', 'EE', 'ES', 'FR', 'GB', 'GR', 'HK', 'HR',
        'HU', 'IE', 'IL', 'IT', 'JP', 'LU', 'LV', 'MX', 'MY', 'NL',
        'NO', 'NZ', 'OM', 'PE', 'PL', 'PT', 'QA', 'RO', 'RU', 'SA', 'SE',
        'SG', 'SK', 'TH', 'TR', 'TW', 'UA', 'US', 'UY', 'VN', 'ZA'
    ];
    if ( $.inArray(country, googlePayCountries) !== -1 ) {
        $('.frb-pm-google').show();
    } else {
        $('.frb-pm-google').remove();
    }

    // Apple Pay
    if ( $('.frb-pm-applepay').length > 0 ) {
        if ( !frb.shouldShowApplePay( country ) ) {
            $('.frb-pm-applepay').remove();
        }
    }

	// Venmo
	var $venmo = $('.frb-pm-venmo');
	if ( country === 'US' && $venmo.length > 0 ) {
		// From MediaWiki:FundraisingBanners/VenmoBrowserCheck.js
		if ( frb.isVenmoSupported() ) {
			$venmo.show();
		} else {
			$venmo.remove();
		}
	} else {
		$venmo.remove();
	}

    /* Add card types class to credit card button, so we can show correct logos */
    if ( frb.cardTypes[country] ) {
        $('.frb-pm-cc').addClass('frb-cctypes-' + frb.cardTypes[country] );
    }
};

/**
 * Check scheduled payment method outages and hide buttons if needed
 *
 * Data at https://meta.wikimedia.org/wiki/MediaWiki:FR2013/Resources/PaymentOutages.js
 * Methods should be labeled with class 'frb-pm-xxxx'
 *
 * @param  {string} country code
 */
frb.checkMethodOutages = function(country) {

    // TODO - can we load this a better way?
    {{MediaWiki:FR2013/Resources/PaymentOutages.js}} // jshint ignore:line
    var now = new Date();

    for (var i = outages.length - 1; i >= 0; i--) {
        if ( now > outages[i].start && now < outages[i].end ) {
            if (outages[i].country === undefined || outages[i].country == country) {
                $('.frb-pm-' + outages[i].method).hide();
            }
        }
    }
};

/**
 * Adjust the amount options and their labels
 *
 * Inputs should have id frb-amt-psX where X is the index number (starting from 1)
 *
 * @param  {Object}  source     - object with amounts e.g. frb.amounts.options7
 * @param  {string}  currency   - currency code e.g. 'USD'
 * @param  {string}  country    - country code  e.g. 'FR' Some currencies can have different options per country.
 * @param  {string}  language   - language code e.g. 'en' For symbol formatting
 * @param  {boolean} useSymbols - use currency symbols on labels or not? (3 vs $3)
 */
frb.localizeAmountOptions = function(source, currency, country, language, useSymbols) {

    var amountOptions = frb.pickAmounts(source, currency, country);

    $('#frb-form input[name="amount"]').each(function(index) {
        var $input = $(this);
        var $label = $input.siblings('label');

        var i = $input.attr('id').replace('frb-amt-ps', '');
        var amount = amountOptions[i-1]; // because IDs start from 1

        if ( amount ) {
            $input.val( amount );
            if ( useSymbols ) {
                $label.text( frb.formatCurrency( currency, amount, language) );
            } else {
                $label.text( frb.formatCurrency( undefined, amount, language) );
            }
        }
    });

};

/**
 * Make an element into a link
 *
 * @param  {string} selector    CSS selector for elements to convert to a link
 * @param  {string} language    Code of language (could be es-419 or pt-br)
 * @param  {string} baseUrl     URL of link (function will add language parameter)
 */
frb.makeLink = function( selector, language, baseUrl ) {
    var url = baseUrl + '&language=' + language;
    $( selector ).each( function() {
        var $link = $( '<a></a>' );
        $link.html( $( this ).html() );
        $link.attr( { href: url, target: '_blank' } );
        $( this ).replaceWith( $link );
    });
};

/**
 * Get the number of banners seen from localStorage
 * @return {number} Number of banners seen
 */
frb.getSeenCount = function () {

    // Force with URL parameter 'impression'
    if ( typeof URLSearchParams === 'function' ) { // not available in old browsers
        var urlParams = new URLSearchParams( window.location.search );
        if ( urlParams.has( 'impression' ) ) {
            return urlParams.get( 'impression' );
        }
    }

    try {
        if ( localStorage ) {
            var identifier = mw.centralNotice.internal.state.campaign.mixins.impressionDiet.cookieName,
                lsName = 'CentralNoticeKV|global|impression_diet_' + identifier,
                diet = JSON.parse( localStorage.getItem( lsName ) );
            if ( diet ) {
                return diet.val.seenCount;
            }
        }
    } catch ( ex ) {
        // do nothing - localStorage is configured not to let us read it, or mixin not set
        return;
    }
};

/**
 * Helper function to do text replacements and wrap them in correct class
 * 
 * @param  {RegExp} regex       Regular expression to replace
 * @param  {string} replacement String to replace it with
 */
frb.textReplace = function( regex, replacement ) {
    $( '.frb' ).each( function( index ) {
        var newHtml = $( this ).html();
        newHtml = newHtml.replace( regex, '<span class="frb-replaced">' + replacement + '</span>' );
        $( this ).html( newHtml );
    });
};

/**
 * Replace elements with preset ask string amounts
 *
 * e.g. class="frb-replace-amt-ps4" will be replaced with amount #4, currently $25 in the US
 *
 * @param  {string} currency - currency code e.g. 'USD'
 * @param  {string} country  - country code  e.g. 'FR'
 * @param  {string} language - language code e.g. 'en' For symbol formatting
 */
frb.replaceCustomAmounts = function( currency, country, language ) {
    var amountOptions = frb.pickAmounts( frb.amounts.options7, currency, country );

    // Old style element replacements
    $( '.frb [class^="frb-replace-amt-ps"]' ).each( function() {
        var i = $( this ).attr( 'class' ).replace( 'frb-replace-amt-ps', '' ),
            amount = amountOptions[ i - 1 ],
            formattedAmount = frb.formatCurrency( currency, amount, language );
        $( this ).html( '<frb-amt>' + formattedAmount + '</frb-amt>' );
    });

    // Text replacements e.g. %amount-4%
    // There is probably a more efficient way to do this, but it's at least fairly simple
    for (var i = 0; i < amountOptions.length; i++) {
        var amount = amountOptions[i],
            formattedAmount,
            regex = new RegExp( '%amount-' + (i+1) + '%', 'gi' );
        if ( frb.textAmountIsoCountries.includes( country ) ) {
            formattedAmount = frb.formatCurrency( undefined, amount, language ) + '&nbsp;' + currency;
        } else {
            formattedAmount = frb.formatCurrency( currency, amount, language );
        }
        frb.textReplace( regex, formattedAmount );
    }
};

/**
 * Get today's date like "December 3" - English only for now
 * 
 * @return {string} Today's date as a string
 */
frb.getDateString = function() {
    var date = new Date(),
        locale = mw.centralNotice.data.uselang + '-' + mw.centralNotice.data.country;
    return date.toLocaleString( locale, { day: 'numeric', month: 'long' } );
};

frb.noRecurringCountries = ['AR'];

/* These countries use potentially ambiguous $ sign.
Use ISO code instead in text (but still $ for buttons) */
frb.textAmountIsoCountries = ['AR', 'CL', 'CO', 'MX'];

$(function() {

    if ( mw.centralNotice.adminUi ) { // T262693
        return;
    }

    var language = mw.centralNotice.data.uselang;
    var variantLanguage; // for pt-br and es-419, note we can only use these for certain links
    var country  = mw.centralNotice.data.country;
    var currency = frb.getCurrency(country);

    if ( language === 'pt' && country === 'BR' ) {
        variantLanguage = 'pt-br';
    } else if ( language === 'es' && ['AR', 'CL', 'CO', 'PE', 'MX', 'UY'].indexOf( country ) !== -1 ) {
        variantLanguage = 'es-419';
    } else {
        variantLanguage = language;
    }

    // Payment methods
    frb.localizeMethods(country);
    frb.checkMethodOutages(country);

    // Preset amounts
    frb.replaceCustomAmounts( currency, country, language );

    // Basic replacements
    $('.frb-replace-currencysymbol').text( frb.formatCurrency( currency, '', language ).replace(' ', '') );
    $('.frb-replace-currencycode').text( currency );

    // Country name
    var countryName;
    if ( frb.countryNames[language] ) {
        countryName = frb.countryNames[language][country] || frb.countryNames.en[country];
    } else {
        countryName = frb.countryNames.en[country];
    }
    $( '.frb-replace-countryname' ).text( countryName );
    frb.textReplace( /%country%/gi, countryName );

    // "in COUNTRY" or equivalent
    var inCountryName;
    if ( frb.inCountryNames[language] ) {
        inCountryName = frb.inCountryNames[language][country] || frb.inCountryNames.en[country];
    } else {
        inCountryName = frb.inCountryNames.en[country];
    }
    $( '.frb-replace-incountryname' ).text( inCountryName );
    frb.textReplace( /%in-country%/gi, inCountryName );

    // Day of week
    // TODO: Replace these with date.toLocaleString so we can drop frb.dayNames? 
    //       Might still need some ways to deal with "this" and capitalization
    var now = new Date();
    var dayNumber = now.getDay();
    var capitalizeText = function( text ) {
        // Capitalize first letter, for use at start of sentence
        return text.charAt(0).toUpperCase() + text.slice(1);
    };

    if ( $('.frb-replace-dayofweek, .frb-replace-dayofweek-capitalize').length > 0 ) {
        if ( frb.dayNames[language] ) {
            $('.frb-replace-dayofweek').text( frb.dayNames[language][dayNumber] );
            $('.frb-replace-dayofweek-capitalize').text( capitalizeText( frb.dayNames[language][dayNumber] ) );
        } else {
            console.log('Warning: banner should contain a day of the week, but no translations found.');
        }
    }

    if ( $('.frb-replace-dayofweek-this, .frb-replace-dayofweek-this-capitalize').length > 0 ) {
        if ( frb.dayNamesThis[language] ) {
            $('.frb-replace-dayofweek-this').text( frb.dayNamesThis[language][dayNumber] );
            $('.frb-replace-dayofweek-this-capitalize').text( capitalizeText( frb.dayNamesThis[language][dayNumber] ) );
        } else {
            console.log('Warning: banner should contain "this DAY", but no translations found.');
        }
    }

    // Simple %weekday% text replacement
    try {
        if ( frb.dayNames[language] ) {
            frb.textReplace( /%weekday%/gi, frb.dayNames[language][dayNumber] );
        } else {
            frb.textReplace( /%weekday%/gi, frb.dayNames['en'][dayNumber] );
        }
    } catch ( error ) {
        console.error( error );
    }

    // Replace %date% with today's date e.g. "December 3"
    try {
        frb.textReplace( /%date%/gi, frb.getDateString() );
    } catch ( error ) {
    	console.log( error );
    }

    // Capitalize
    $('.frb-capitalize').text(function( index, text ) {
        return text.charAt(0).toUpperCase() + text.slice(1);
    });

    // Replace %average%, %minimum% and %amount%
    var average = frb.pickAmounts( frb.amounts.averages, currency, country ),
        ifEveryone = frb.pickAmounts( frb.amounts.ifEveryone, currency, country ),
        avgString,
        ifString;

    if ( frb.textAmountIsoCountries.indexOf(country) !== -1 ) {
        avgString = frb.formatCurrency( undefined, average, language ) + '&nbsp;' + currency;
        ifString  = frb.formatCurrency( undefined, ifEveryone, language ) + '&nbsp;' + currency;
    } else {
        avgString = frb.formatCurrency( currency, average, language ).replace( /\.$/, '' ); // strip any period from end for use in running text
        ifString  = frb.formatCurrency( currency, ifEveryone, language ).replace( /\.$/, '' );
    }
    frb.textReplace( /%average%/gi, avgString );
    frb.textReplace( /%minimum%/gi, ifString );
    frb.textReplace( /%amount%/gi,  ifString );

    /**
     * Call a function on every text node contained by a root node.
     *
     * Used so we can do text replacements without accidentally clobbering html and scripts
     *
     * @param  {Node}     rootNode The Node object whose descendants will be recursed through
     * @param  {Function} callback Callback function that receives a Node as its only argument
     */
    function eachTextNode( rootNode, callback ) {
        for ( var node = rootNode.firstChild; node !== null; node = node.nextSibling ) {
            if ( node.nodeType === Node.TEXT_NODE ) {
                callback( node );
            } else if ( node.nodeType === Node.ELEMENT_NODE ) {
                eachTextNode( node, callback );
            }
        }
    }

    // French spacing: replace space before punctuation with &nbsp;
    if ( language === 'fr' ) {
        var bannerRootElements = document.getElementsByClassName( 'frb' );
        for ( var i = 0; i < bannerRootElements.length; i++ ) {
            eachTextNode( bannerRootElements[i], function( node ) {
                node.textContent = node.textContent.replace( / ([!?;:%])/g, '\u00a0$1' );
            });
        }
    }

    // Links (in smallprint) TODO: merge with frb.makeLink()
    $('.frb-localize-links a').each(function() {
        // Add parameters for LandingCheck
        var uri = new mw.Uri( $(this).attr('href') );
        uri.extend({
            country:      country,
            language:     variantLanguage,
            uselang:      variantLanguage,
            wmf_medium:   'sitenotice',
            wmf_campaign: mw.centralNotice.data.campaign || 'test',
            wmf_source:   mw.centralNotice.data.banner
        });
        $(this).attr('href', uri.toString());
        $(this).attr('target', '_blank'); // Make links open in new tab
    });

    // Add links
    frb.makeLink( '.frb-link-privacy', variantLanguage, 'https://foundation.wikimedia.org/wiki/Special:LandingCheck?basic=true&landing_page=Donor_privacy_policy' );
    frb.makeLink( '.frb-link-tax',     variantLanguage, 'https://donate.wikimedia.org/wiki/Special:LandingCheck?basic=true&landing_page=Tax_deductibility' );
    frb.makeLink( '.frb-link-cancel',  variantLanguage, 'https://donate.wikimedia.org/wiki/Special:LandingCheck?basic=true&landing_page=Cancel_or_change_recurring_giving' );

    // Legal text variants
    if (country === 'US') {
        $('.frb-legal-US').show();
        $('.frb-legal-nonUS, .frb-legal-NL').hide();
    } else if (country === 'NL') {
        $('.frb-legal-NL').show();
        $('.frb-legal-US, .frb-legal-nonUS').hide();
    } else {
        $('.frb-legal-nonUS').show();
        $('.frb-legal-US, .frb-legal-NL').hide();
    }

    // Quick hack for American/British/Commonwealth English differences
    if ( country === 'US' ) {
        $('.frb-lang-enUS').show();
        $('.frb-lang-en5C').hide();
    } else {
        $('.frb-lang-enUS').hide();
        $('.frb-lang-en5C').show();
    }

    // Add this so they get white-space: nowrap from CSS
    $('.frb-ptf-fee, .frb-ptf-total, .frb-upsell-ask').addClass('frb-replaced');

    // Where Remind Me Later should be shown
    var rmlCountries = ['US', 'CA', 'GB', 'IE', 'AU', 'NZ', 'ES',
                        'IN', 'FR', 'JP', 'NL', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY'];
    var rmlLanguages = ['en', 'nl', 'ja', 'it', 'sv', 'pt', 'cs', 'es', 'ca'];
    var rmlEnabled = !frb.isEndowment && rmlCountries.indexOf(country) !== -1 && rmlLanguages.indexOf(language) !== -1;

    if ( rmlEnabled ) {
        $('.frb').addClass('frb-rml-enabled');
    } else {
        $('.frb').addClass('frb-rml-disabled');
    }

});

/* == end of MediaWiki:FundraisingBanners/LocalizeJS-2022.js == */