MediaWiki:FundraisingBanners/LocalizeJS-2017.js

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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* jshint maxerr: 600 */
/* == MediaWiki:FundraisingBanners/LocalizeJS-2017.js == */
/* Note this has been replaced with 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 '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 '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';
        // Fall back to USD
        default:
            return 'USD';
    }
};

/**
 * Format a currency value
 *
 * @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', // Adyen
    'IT' : 'vmaj',
    'LU' : 'vmaj',
    'LV' : 'vma',
    'NL' : 'vmaj', // Adyen
    'PT' : 'vmaj',
    'SK' : 'vmaj',
    // Others
    'DK' : 'vma',
    'HU' : 'vma',
    'IL' : 'vmad', // Adyen
    'JP' : 'vmj',
    'MY' : 'vmaj',
    'NO' : 'vma',
    'PL' : 'vma',
    'RO' : 'vma',
    'SE' : 'vma',
    'UA' : 'vma', // Adyen
    'ZA' : 'vm',
    'IN' : 'vmar' // dLocal
};

/**
 * 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();

    // Monthly Adyen credit card is allowed now
    // if ( frb.ccAdyenCountries.indexOf( country ) !== -1 ) {
    //     $('.frb-pm-cc').addClass('no-monthly');
    // }

    // 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'],
        'bpay'     : [],
        'ideal'    : ['NL'],
        'bt'       : ['BR', 'AR', 'CO', 'CL', 'PE', 'IN', 'ZA'], // Bank Transfer (Astropay)
        'cash'     : ['BR', 'MX', 'AR', 'CO', 'PE', 'UY'],  // 'Cash' methods (Astropay)
        'pix'      : ['BR']
    };

    // Methods with different labels per country

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

    if (language === 'en') {

        if (country === 'BR') {
            cashTranslation = 'Boleto';
        }

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

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

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

        if (country === 'AR') {
            btTranslation   = 'Transferencia Bancaria';
            cashTranslation = 'Efectivo';
        }
        if (country === 'CL') {
            btTranslation   = 'WebPay';
        }
        if (country === 'CO') {
            btTranslation   = 'PSE Pagos';
            cashTranslation = 'Efectivo';
        }
        if (country === 'MX') {
            cashTranslation = 'Efectivo';
        }
        if (country === 'PE') {
            btTranslation   = 'Transferencia Bancaria';
            cashTranslation = 'Efectivo';
        }
        if (country === 'UY') {
            cashTranslation = 'Efectivo';
        }

    }

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

    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', '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', 'PL', 'PT', 'QA', 'RO', 'RU', 'SA', 'SE',
        'SG', 'SK', 'TH', 'TR', 'TW', 'UA', 'US', '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();
        }
    }

    /* 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;
    }
};

/**
 * Replace placeholders with number of banners seen
 * @param {string} Language code
 */
frb.replaceSeenCount = function( language ) {

    var ordinalNums = {
            'en' : [ '', '1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th' ],
            'ja' : [ '', '初めて', '2回目', '3回目', '4回目', '5回目', '6回目', '7回目', '8回目', '9回目', '10回目' ],
            'fr':  [ '', '1er', '2e', '3e', '4e', '5e', '6e', '7e', '8e', '9e', '10e' ],
            'nl' : [ '', '1e', '2e', '3e', '4e', '5e', '6e', '7e', '8e', '9e', '10e' ]
        },
        ordinalWords = {
            'en' : [ '', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth' ],
            'it' : [ '', 'la prima', 'la seconda', 'la terza', 'la quarta', 'la quinta', 'la sesta', 'la settima', "l'ottava", 'la nona', 'la decima' ],
            'sv' : [ '', 'första', 'andra', 'tredje', 'fjärde', 'femte', 'sjätte', 'sjunde', 'åttonde', 'nionde', 'tionde' ],
            'es' : [ '', 'primera', 'segunda', 'tercera', 'cuarta', 'quinta', 'sexta', 'séptima', 'octava', 'novena', 'décima' ],
            'pt' : [ '', 'primeira', 'segunda', 'terceira', 'quarta', 'quinta', 'sexta', 'sétima', 'oitava', 'nona', 'décima' ],
            'aa' : [ '', 'primera', 'segunda', 'tercera', 'cuarta', 'quinta', 'sexta', 'séptima', 'octava', 'novena', 'décima' ],
            'ja' : [ '', '初めて', '2回目', '3回目', '4回目', '5回目', '6回目', '7回目', '8回目', '9回目', '10回目' ],
            'fr' : [ '', 'première', 'deuxième', 'troisième', 'quatrième', 'cinquième', 'sixième', 'septième', 'huitième', 'neuvième', 'dixième' ],
            'de' : [ '', 'erste', 'zweite', 'dritte', 'vierte', 'fünfte', 'sechste', 'siebente', 'achte', 'neunte', 'zehnte' ]
        },
        textFragments = {
            'en' : [
                'for the 1st time recently',
                'for the 2nd time recently',
                'for the 3rd time recently',
                'for the 4th time recently',
                'for the 5th time recently',
                'for the 6th time recently',
                'for the 7th time recently',
                'for the 8th time recently',
                'for the 9th time recently',
                'for the 10th time recently'
            ]
        };
    var getReplaceText = function( data, language, seenCount ) {
        if ( data[ language ] ) {
            return data[ language ][ seenCount ];
        } else {
            return data[ 'en' ][ seenCount ];
        }
    };

    var seenCount = frb.getSeenCount();
    if ( seenCount ) {
        $( '.frb-replace-seenCount' ).text( seenCount );
        $( '.frb-replace-seenCount-ordinalNum' ).text( getReplaceText( ordinalNums, language, seenCount ) );
        $( '.frb-replace-seenCount-ordinalWord' ).text( getReplaceText( ordinalWords, language, seenCount ) );
        $( '.frb-replace-seenCount-textFragment' ).text( getReplaceText( textFragments, language, seenCount ) );
    }

};

/**
 * 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 );
    $( '.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>' );
    });
};

frb.noRecurringCountries = ['AR', 'CL', 'CO', 'MX', 'PE', 'UY', 'BR', 'IN'];
frb.ccAdyenCountries     = ['FR', 'IL', 'UA'];

/* 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 );

    // Count banners seen
    frb.replaceSeenCount( language );

    // 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 );

    // Day of week
    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.');
        }
    }

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

    // Replace device with iPad if needed
    var ua = navigator.userAgent;
    if ( ua.match( /ipad/i ) ) {
        $('.frb-replace-device').text( frb.iPadTranslations[language] || frb.iPadTranslations.en );
    }

    // Replace %AVERAGE% and %MINIMUM%
    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' ).each( function( index ) {
        var newHtml = $(this).html();
        newHtml = newHtml.replace( /%AVERAGE%/g, '<frb-amt>' + avgString + '</frb-amt>' );
        newHtml = newHtml.replace( /%MINIMUM%/g, '<frb-amt>' + ifString + '</frb-amt>' );
        $( this ).html( newHtml );
    });

    /**
     * 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,
            utm_medium:   'sitenotice',
            utm_campaign: mw.centralNotice.data.campaign || 'test',
            utm_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();
    }

    // Where Remind Me Later should be shown
    var rmlCountries = ['US', 'CA', 'GB', 'IE', 'AU', 'NZ',
                        'IT', 'NL', 'JP', 'FR', 'SE', 'ZA', 'IN',
                        'AR', 'BR', 'CL', 'CO', 'PE', 'UY', 'MX'];
    var rmlLanguages = ['en', 'fr', 'nl', 'ja', 'it', 'sv', 'pt', 'es'];
    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-2017.js == */