@larsgw/wikibase-sdk
Version:
utils functions to query a Wikibase instance and simplify its results
2,283 lines (2,268 loc) • 80.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function isIdBuilder(regex) {
return (id) => typeof id === 'string' && new RegExp(regex.source, regex.flags).test(id);
}
const isNumericId = isIdBuilder(/^[1-9][0-9]*$/);
const isEntityId = isIdBuilder(/^((Q|P|L|M)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)$/);
const isEntitySchemaId = isIdBuilder(/^E[1-9][0-9]*$/);
const isItemId = isIdBuilder(/^Q[1-9][0-9]*$/);
const isPropertyId = isIdBuilder(/^P[1-9][0-9]*$/);
const isLexemeId = isIdBuilder(/^L[1-9][0-9]*$/);
const isFormId = isIdBuilder(/^L[1-9][0-9]*-F[1-9][0-9]*$/);
const isSenseId = isIdBuilder(/^L[1-9][0-9]*-S[1-9][0-9]*$/);
const isMediaInfoId = isIdBuilder(/^M[1-9][0-9]*$/);
const isGuid = isIdBuilder(/^((Q|P|L|M)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)\$[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
const isHash = isIdBuilder(/^[0-9a-f]{40}$/);
const isRevisionId = isIdBuilder(/^\d+$/);
const isNonNestedEntityId = isIdBuilder(/^(Q|P|L|M)[1-9][0-9]*$/);
function isPropertyClaimsId(id) {
if (typeof id !== 'string')
return false;
const [entityId, propertyId] = id.split('#');
return isEntityId(entityId) && isPropertyId(propertyId);
}
function isEntityPageTitle(title) {
if (typeof title !== 'string')
return false;
if (title.startsWith('Item:')) {
return isItemId(title.substring(5));
}
if (title.startsWith('Lexeme:')) {
return isLexemeId(title.substring(7));
}
if (title.startsWith('Property:')) {
return isPropertyId(title.substring(9));
}
return isItemId(title);
}
function getNumericId(id) {
if (!isNonNestedEntityId(id))
throw new Error(`invalid entity id: ${id}`);
return id.replace(/^(Q|P|L|M)/, '');
}
function getImageUrl(filename, width) {
let url = `https://commons.wikimedia.org/wiki/Special:FilePath/${filename}`;
if (typeof width === 'number')
url += `?width=${width}`;
return url;
}
function getEntityIdFromGuid(guid) {
const parts = guid.split(/[$-]/);
if (parts.length === 6) {
// Examples:
// - q520$BCA8D9DE-B467-473B-943C-6FD0C5B3D02C
// - P6216-a7fd6230-496e-6b47-ca4a-dcec5dbd7f95
return parts[0].toUpperCase();
}
else if (parts.length === 7) {
// Examples:
// - L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48
// - L525-F2-52c9b382-02f5-4413-9923-26ade74f5a0d
return parts.slice(0, 2).join('-').toUpperCase();
}
else {
throw new Error(`invalid guid: ${guid}`);
}
}
var helpers = /*#__PURE__*/Object.freeze({
__proto__: null,
getEntityIdFromGuid: getEntityIdFromGuid,
getImageUrl: getImageUrl,
getNumericId: getNumericId,
isEntityId: isEntityId,
isEntityPageTitle: isEntityPageTitle,
isEntitySchemaId: isEntitySchemaId,
isFormId: isFormId,
isGuid: isGuid,
isHash: isHash,
isItemId: isItemId,
isLexemeId: isLexemeId,
isMediaInfoId: isMediaInfoId,
isNonNestedEntityId: isNonNestedEntityId,
isNumericId: isNumericId,
isPropertyClaimsId: isPropertyClaimsId,
isPropertyId: isPropertyId,
isRevisionId: isRevisionId,
isSenseId: isSenseId
});
/** Example: keep only 'fr' in 'fr_FR' */
/**
* a polymorphism helper:
* accept either a string or an array and return an array
*/
function forceArray(array) {
if (typeof array === 'string') {
return [array];
}
if (Array.isArray(array)) {
// TODO: return readonly array
return [...array];
}
return [];
}
/** simplistic implementation to filter-out arrays and null */
function isPlainObject(obj) {
return Boolean(obj) && typeof obj === 'object' && !Array.isArray(obj);
}
// encodeURIComponent ignores !, ', (, ), and *
// cf https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
const fixedEncodeURIComponent = (str) => {
return encodeURIComponent(str).replace(/[!'()*]/g, encodeCharacter);
};
const replaceSpaceByUnderscores = (str) => str.replace(/\s/g, '_');
function uniq(array) {
return Array.from(new Set(array));
}
const encodeCharacter = (char) => '%' + char.charCodeAt(0).toString(16);
function rejectObsoleteInterface(args) {
if (args.length !== 1 || !isPlainObject(args[0])) {
throw new Error(`Since wikibase-sdk v9.0.0, this function expects arguments to be passed in an object
See https://github.com/maxlath/wikibase-sdk/blob/main/CHANGELOG.md`);
}
}
/**
* Checks if the `element` is of one of the entries of `all`
* @example const isSite: site is Site = isOfType(sites, site)
*/
function isOfType(all, element) {
return typeof element === 'string' && all.includes(element);
}
/** key is a key on the object */
function isAKey(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
/** like Object.entries() but with typed keys */
function typedEntries(input) {
// @ts-expect-error string is not assignable to K as K is more specific
return Object.entries(input);
}
/** like Object.keys() but with typed keys */
function typedKeys(obj) {
return Object.keys(obj);
}
function wikibaseTimeToDateObject(wikibaseTime) {
// Also accept claim datavalue.value objects
if (typeof wikibaseTime === 'object') {
wikibaseTime = wikibaseTime.time;
}
const sign = wikibaseTime[0];
let [yearMonthDay, withinDay] = wikibaseTime.slice(1).split('T');
// Wikidata generates invalid ISO dates to indicate precision
// ex: +1990-00-00T00:00:00Z to indicate 1990 with year precision
yearMonthDay = yearMonthDay.replace(/-00/g, '-01');
const rest = `${yearMonthDay}T${withinDay}`;
return fullDateData(sign, rest);
}
const fullDateData = (sign, rest) => {
const year = rest.split('-')[0];
const needsExpandedYear = sign === '-' || year.length > 4;
return needsExpandedYear ? expandedYearDate(sign, rest, year) : new Date(rest);
};
const expandedYearDate = (sign, rest, year) => {
let date;
// Using ISO8601 expanded notation for negative years or positive
// years with more than 4 digits: adding up to 2 leading zeros
// when needed. Can't find the documentation again, but testing
// with `new Date(date)` gives a good clue of the implementation
if (year.length === 4) {
date = `${sign}00${rest}`;
}
else if (year.length === 5) {
date = `${sign}0${rest}`;
}
else {
date = sign + rest;
}
return new Date(date);
};
const toEpochTime = (wikibaseTime) => wikibaseTimeToDateObject(wikibaseTime).getTime();
const toISOString = (wikibaseTime) => wikibaseTimeToDateObject(wikibaseTime).toISOString();
// A date format that knows just three precisions:
// 'yyyy', 'yyyy-mm', and 'yyyy-mm-dd' (including negative and non-4 digit years)
// Should be able to handle the old and the new Wikidata time:
// - in the old one, units below the precision where set to 00
// - in the new one, those months and days are set to 01 in those cases,
// so when we can access the full claim object, we check the precision
// to recover the old format
const toSimpleDay = (wikibaseTime) => {
// Also accept claim datavalue.value objects, and actually prefer those,
// as we can check the precision
if (typeof wikibaseTime === 'object') {
const { time, precision } = wikibaseTime;
// Year precision
if (precision === 9)
wikibaseTime = time.replace('-01-01T', '-00-00T');
// Month precision
else if (precision === 10)
wikibaseTime = time.replace('-01T', '-00T');
else
wikibaseTime = time;
}
return wikibaseTime.split('T')[0]
// Remove positive years sign
.replace(/^\+/, '')
// Remove years padding zeros
.replace(/^(-?)0+/, '$1')
// Remove days if not included in the Wikidata date precision
.replace(/-00$/, '')
// Remove months if not included in the Wikidata date precision
.replace(/-00$/, '');
};
const wikibaseTimeToEpochTime = toEpochTime;
const wikibaseTimeToISOString = (value) => {
try {
return toISOString(value);
}
catch (_a) {
const { sign, yearMonthDay, withinDay } = recoverDateAfterError(value);
return `${sign}${yearMonthDay}T${withinDay}`;
}
};
const wikibaseTimeToSimpleDay = (value) => {
try {
return toSimpleDay(value);
}
catch (_a) {
const { sign, yearMonthDay } = recoverDateAfterError(value);
return `${sign}${yearMonthDay}`;
}
};
function recoverDateAfterError(value) {
value = typeof value === 'string' ? value : value.time;
const sign = value[0];
let [yearMonthDay, withinDay] = value.slice(1).split('T');
if (!sign || !yearMonthDay || !withinDay) {
throw new Error('TimeInput is invalid: ' + JSON.stringify(value));
}
yearMonthDay = yearMonthDay.replace(/-00/g, '-01');
return { sign, yearMonthDay, withinDay };
}
var timeHelpers = /*#__PURE__*/Object.freeze({
__proto__: null,
wikibaseTimeToDateObject: wikibaseTimeToDateObject,
wikibaseTimeToEpochTime: wikibaseTimeToEpochTime,
wikibaseTimeToISOString: wikibaseTimeToISOString,
wikibaseTimeToSimpleDay: wikibaseTimeToSimpleDay
});
function stringValue(datavalue) {
return datavalue.value;
}
function monolingualtext(datavalue, options) {
return options.keepRichValues ? datavalue.value : datavalue.value.text;
}
function entity(datavalue, options) {
const { entityPrefix: prefix } = options;
const { value } = datavalue;
let id;
if (value.id) {
id = value.id;
}
else {
// Legacy
const letter = entityLetter[value['entity-type']];
id = `${letter}${value['numeric-id']}`;
}
return typeof prefix === 'string' ? `${prefix}:${id}` : id;
}
const entityLetter = {
item: 'Q',
'entity-schema': 'E',
lexeme: 'L',
property: 'P',
form: 'F',
sense: 'S',
};
function quantity(datavalue, options) {
const { value } = datavalue;
const amount = parseFloat(value.amount);
if (options.keepRichValues) {
const richValue = {
amount: parseFloat(value.amount),
// ex: http://www.wikidata.org/entity/
unit: value.unit.replace(/^https?:\/\/.*\/entity\//, ''),
};
if (value.upperBound != null)
richValue.upperBound = parseFloat(value.upperBound);
if (value.lowerBound != null)
richValue.lowerBound = parseFloat(value.lowerBound);
return richValue;
}
else {
return amount;
}
}
function coordinate(datavalue, options) {
if (options.keepRichValues) {
return datavalue.value;
}
else {
return [datavalue.value.latitude, datavalue.value.longitude];
}
}
function time(datavalue, options) {
let timeValue;
if (typeof options.timeConverter === 'function') {
timeValue = options.timeConverter(datavalue.value);
}
else {
timeValue = getTimeConverter(options.timeConverter)(datavalue.value);
}
if (options.keepRichValues) {
const { timezone, before, after, precision, calendarmodel } = datavalue.value;
return { time: timeValue, timezone, before, after, precision, calendarmodel };
}
else {
return timeValue;
}
}
// Each time converter should be able to accept 2 keys of arguments:
// - either datavalue.value objects (prefered as it gives access to the precision)
// - or the time string (datavalue.value.time)
const timeConverters = {
iso: wikibaseTimeToISOString,
epoch: wikibaseTimeToEpochTime,
'simple-day': wikibaseTimeToSimpleDay,
none: (wikibaseTime) => typeof wikibaseTime === 'string' ? wikibaseTime : wikibaseTime.time,
};
function getTimeConverter(key = 'iso') {
const converter = timeConverters[key];
if (!converter)
throw new Error(`invalid converter key: ${JSON.stringify(key).substring(0, 100)}`);
return converter;
}
const parsers = {
commonsMedia: stringValue,
'external-id': stringValue,
'entity-schema': entity,
'geo-shape': stringValue,
'globe-coordinate': coordinate,
math: stringValue,
monolingualtext,
'musical-notation': stringValue,
quantity,
string: stringValue,
'tabular-data': stringValue,
time,
url: stringValue,
'wikibase-form': entity,
'wikibase-item': entity,
'wikibase-lexeme': entity,
'wikibase-property': entity,
'wikibase-sense': entity,
};
const legacyParsers = {
'musical notation': parsers['musical-notation'],
// Known case: mediainfo won't have datatype="globe-coordinate", but datavalue.type="globecoordinate"
globecoordinate: parsers['globe-coordinate'],
};
function parseSnak(datatype, datavalue, options) {
let parser;
if (datatype) {
// @ts-expect-error legacyParsers datatypes aren't in DataValueByDataType
parser = parsers[datatype] || legacyParsers[datatype];
}
else {
parser = parsers[datavalue.type];
}
if (!parser) {
throw new Error(`${datatype} claim parser isn't implemented. Please report to https://github.com/maxlath/wikibase-sdk/issues`);
}
return parser(datavalue, options);
}
function truthyPropertyClaims(propertyClaims) {
const aggregate = {};
for (const claim of propertyClaims) {
const { rank } = claim;
aggregate[rank] = aggregate[rank] || [];
aggregate[rank].push(claim);
}
// on truthyness: https://www.mediawiki.org/wiki/Wikibase/Indexing/RDF_Dump_Format#Truthy_statements
return aggregate.preferred || aggregate.normal || [];
}
function nonDeprecatedPropertyClaims(propertyClaims) {
return propertyClaims.filter(claim => claim.rank !== 'deprecated');
}
function truthyClaims(claims) {
const truthClaimsOnly = {};
for (const [property, value] of typedEntries(claims)) {
truthClaimsOnly[property] = truthyPropertyClaims(value);
}
return truthClaimsOnly;
}
var rankHelpers = /*#__PURE__*/Object.freeze({
__proto__: null,
nonDeprecatedPropertyClaims: nonDeprecatedPropertyClaims,
truthyClaims: truthyClaims,
truthyPropertyClaims: truthyPropertyClaims
});
/**
* Tries to replace wikidata deep snak object by a simple value
* e.g. a string, an entity Qid or an epoch time number
* Expects a single snak object
* Ex: entity.claims.P369[0]
*/
function simplifySnak(snak, options = {}) {
const { keepTypes, keepSnaktypes, keepHashes } = parseKeepOptions(options);
let value;
const { datatype, datavalue, snaktype, hash } = snak;
if (datavalue) {
value = parseSnak(datatype, datavalue, options);
}
else {
if (snaktype === 'somevalue')
value = options.somevalueValue;
else if (snaktype === 'novalue')
value = options.novalueValue;
else
throw new Error('no datavalue or special snaktype found');
}
// No need to test keepHashes as it has no effect if neither
// keepQualifiers or keepReferences is true
if (keepTypes || keepSnaktypes || keepHashes) {
// When keeping qualifiers or references, the value becomes an object
// instead of a direct value
const valueObj = { value };
if (keepTypes)
valueObj.type = datatype;
if (keepSnaktypes)
valueObj.snaktype = snaktype;
if (keepHashes)
valueObj.hash = hash;
return valueObj;
}
else {
return value;
}
}
function simplifyClaim(claim, options = {}) {
const { keepQualifiers, keepReferences, keepIds, keepTypes, keepSnaktypes, keepRanks } = parseKeepOptions(options);
const { mainsnak, rank } = claim;
const value = simplifySnak(mainsnak, options);
// No need to test keepHashes as it has no effect if neither
// keepQualifiers or keepReferences is true
if (!(keepQualifiers || keepReferences || keepIds || keepTypes || keepSnaktypes || keepRanks)) {
return value;
}
// When keeping other attributes, the value becomes an object instead of a direct value
let valueObj = { value };
if (isPlainObject(value) && 'value' in value) {
valueObj = value;
}
else {
valueObj = { value };
}
if (keepRanks)
valueObj.rank = rank;
if (keepQualifiers) {
valueObj.qualifiers = simplifyQualifiers(claim.qualifiers, options);
}
if (keepReferences) {
claim.references = claim.references || [];
valueObj.references = simplifyReferences(claim.references, options);
}
if (keepIds)
valueObj.id = claim.id;
return valueObj;
}
function simplifyClaims(claims, options = {}) {
const { propertyPrefix } = options;
const simplified = {};
for (let [propertyId, propertyArray] of typedEntries(claims)) {
if (propertyPrefix) {
propertyId = propertyPrefix + ':' + propertyId;
}
simplified[propertyId] = simplifyPropertyClaims(propertyArray, options);
}
return simplified;
}
function simplifyPropertyClaims(propertyClaims, options = {}) {
// Avoid to throw on empty inputs to allow to simplify claims array
// without having to know if the entity as claims for this property
// Ex: simplifyPropertyClaims(entity.claims.P124211616)
if (propertyClaims == null || propertyClaims.length === 0)
return [];
const { keepNonTruthy, keepNonDeprecated } = parseKeepOptions(options);
const { minTimePrecision } = options;
if (keepNonDeprecated) {
propertyClaims = nonDeprecatedPropertyClaims(propertyClaims);
}
else if (!(keepNonTruthy)) {
propertyClaims = truthyPropertyClaims(propertyClaims);
}
const simplifiedArray = [];
for (const claim of propertyClaims) {
const isDroppedClaim = timeSnakPrecisionIsTooLow(claim.mainsnak, minTimePrecision);
if (!isDroppedClaim) {
const simplifiedClaim = simplifyClaim(claim, options);
// Filter-out novalue and somevalue claims,
// unless a novalueValue or a somevalueValue is passed in options
// Considers null as defined
if (simplifiedClaim !== undefined)
simplifiedArray.push(simplifiedClaim);
}
}
// Deduplicate values unless we return a rich value object
if (simplifiedArray[0] && typeof simplifiedArray[0] !== 'object') {
return uniq(simplifiedArray);
}
else {
return simplifiedArray;
}
}
function simplifySnaks(snaks = {}, options = {}) {
const { propertyPrefix } = options;
const simplified = {};
for (let [propertyId, propertyArray] of typedEntries(snaks)) {
if (propertyPrefix) {
propertyId = propertyPrefix + ':' + propertyId;
}
simplified[propertyId] = simplifyPropertySnaks(propertyArray, options);
}
return simplified;
}
function simplifyPropertySnaks(propertySnaks, options = {}) {
if (propertySnaks == null || propertySnaks.length === 0)
return [];
const { minTimePrecision } = options;
const simplifiedArray = [];
for (const snak of propertySnaks) {
const isDroppedSnak = timeSnakPrecisionIsTooLow(snak, minTimePrecision);
if (!isDroppedSnak) {
const simplifiedSnak = simplifySnak(snak, options);
// Filter-out novalue and somevalue snaks,
// unless a novalueValue or a somevalueValue is passed in options
// Considers null as defined
if (simplifiedSnak !== undefined)
simplifiedArray.push(simplifiedSnak);
}
}
// Deduplicate values unless we return a rich value object
if (simplifiedArray[0] && typeof simplifiedArray[0] !== 'object') {
return uniq(simplifiedArray);
}
else {
return simplifiedArray;
}
}
function simplifyQualifiers(qualifiers, options = {}) {
return simplifySnaks(qualifiers, options);
}
function simplifyPropertyQualifiers(propertyQualifiers, options = {}) {
return simplifyPropertySnaks(propertyQualifiers, options);
}
function simplifyQualifier(qualifier, options = {}) {
return simplifySnak(qualifier, options);
}
function simplifyReferences(references, options = {}) {
return references.map(reference => simplifyReference(reference, options));
}
function simplifyReference(reference, options = {}) {
const snaks = simplifySnaks(reference.snaks, options);
if (options.keepHashes)
return { snaks, hash: reference.hash };
else
return snaks;
}
/** @deprecated use the new function name simplifyReference instead */
const simplifyReferenceRecord = simplifyReference;
const keepOptions = ['keepQualifiers', 'keepReferences', 'keepIds', 'keepHashes', 'keepTypes', 'keepSnaktypes', 'keepRanks', 'keepRichValues'];
const parseKeepOptions = (options = {}) => {
if (options.keepAll) {
for (const optionName of keepOptions) {
if (options[optionName] == null)
options[optionName] = true;
}
}
return options;
};
function timeSnakPrecisionIsTooLow(snak, minTimePrecision) {
if (minTimePrecision == null)
return false;
if (snak.datatype !== 'time' || snak.snaktype !== 'value')
return false;
const { value } = snak.datavalue;
return value.precision < minTimePrecision;
}
function singleValue(data) {
const simplified = {};
for (const [lang, obj] of typedEntries(data)) {
simplified[lang] = obj != null ? obj.value : null;
}
return simplified;
}
function multiValue(data) {
const simplified = {};
for (const [lang, obj] of typedEntries(data)) {
simplified[lang] = obj != null ? obj.map(o => o.value) : [];
}
return simplified;
}
function simplifyLabels(labels) {
return singleValue(labels);
}
function simplifyDescriptions(descriptions) {
return singleValue(descriptions);
}
function simplifyAliases(aliases) {
return multiValue(aliases);
}
function simplifyLemmas(lemmas) {
return singleValue(lemmas);
}
function simplifyRepresentations(representations) {
return singleValue(representations);
}
function simplifyGlosses(glosses) {
return singleValue(glosses);
}
const simplifyForm = (form, options = {}) => {
const { id, representations, grammaticalFeatures, claims } = form;
if (!isFormId(id))
throw new Error('invalid form object');
return {
id,
representations: simplifyRepresentations(representations),
grammaticalFeatures,
claims: simplifyClaims(claims, options),
};
};
const simplifyForms = (forms, options = {}) => forms.map(form => simplifyForm(form, options));
const simplifySense = (sense, options = {}) => {
const { id, glosses, claims } = sense;
if (!isSenseId(id))
throw new Error('invalid sense object');
return {
id,
glosses: simplifyGlosses(glosses),
claims: simplifyClaims(claims, options),
};
};
function simplifySenses(senses, options = {}) {
return senses.map(sense => simplifySense(sense, options));
}
// Generated by 'npm run update-wikimedia-constants'
const specialSites = {
commonswiki: 'commons',
foundationwiki: 'foundation',
mediawikiwiki: 'mediawiki',
metawiki: 'meta',
outreachwiki: 'outreach',
sourceswiki: 'sources',
specieswiki: 'species',
wikidatawiki: 'wikidata',
wikifunctionswiki: 'wikifunctions',
wikimaniawiki: 'wikimania',
};
const sites = [
'aawiki',
'aawikibooks',
'aawiktionary',
'abwiki',
'abwiktionary',
'acewiki',
'adywiki',
'afwiki',
'afwikibooks',
'afwikiquote',
'afwiktionary',
'akwiki',
'akwikibooks',
'akwiktionary',
'alswiki',
'alswikibooks',
'alswikiquote',
'alswiktionary',
'altwiki',
'amiwiki',
'amwiki',
'amwikiquote',
'amwiktionary',
'angwiki',
'angwikibooks',
'angwikiquote',
'angwikisource',
'angwiktionary',
'anpwiki',
'anwiki',
'anwiktionary',
'arcwiki',
'arwiki',
'arwikibooks',
'arwikinews',
'arwikiquote',
'arwikisource',
'arwikiversity',
'arwiktionary',
'arywiki',
'arzwiki',
'astwiki',
'astwikibooks',
'astwikiquote',
'astwiktionary',
'aswiki',
'aswikibooks',
'aswikiquote',
'aswikisource',
'aswiktionary',
'atjwiki',
'avkwiki',
'avwiki',
'avwiktionary',
'awawiki',
'aywiki',
'aywikibooks',
'aywiktionary',
'azbwiki',
'azwiki',
'azwikibooks',
'azwikiquote',
'azwikisource',
'azwiktionary',
'banwiki',
'banwikisource',
'barwiki',
'bat_smgwiki',
'bawiki',
'bawikibooks',
'bbcwiki',
'bclwiki',
'bclwikiquote',
'bclwiktionary',
'bdrwiki',
'be_x_oldwiki',
'bewiki',
'bewikibooks',
'bewikiquote',
'bewikisource',
'bewiktionary',
'bewwiki',
'bgwiki',
'bgwikibooks',
'bgwikinews',
'bgwikiquote',
'bgwikisource',
'bgwiktionary',
'bhwiki',
'bhwiktionary',
'biwiki',
'biwikibooks',
'biwiktionary',
'bjnwiki',
'bjnwikiquote',
'bjnwiktionary',
'blkwiki',
'blkwiktionary',
'bmwiki',
'bmwikibooks',
'bmwikiquote',
'bmwiktionary',
'bnwiki',
'bnwikibooks',
'bnwikiquote',
'bnwikisource',
'bnwikivoyage',
'bnwiktionary',
'bowiki',
'bowikibooks',
'bowiktionary',
'bpywiki',
'brwiki',
'brwikiquote',
'brwikisource',
'brwiktionary',
'bswiki',
'bswikibooks',
'bswikinews',
'bswikiquote',
'bswikisource',
'bswiktionary',
'btmwiki',
'btmwiktionary',
'bugwiki',
'bxrwiki',
'cawiki',
'cawikibooks',
'cawikinews',
'cawikiquote',
'cawikisource',
'cawiktionary',
'cbk_zamwiki',
'cdowiki',
'cebwiki',
'cewiki',
'chowiki',
'chrwiki',
'chrwiktionary',
'chwiki',
'chwikibooks',
'chwiktionary',
'chywiki',
'ckbwiki',
'ckbwiktionary',
'commonswiki',
'cowiki',
'cowikibooks',
'cowikiquote',
'cowiktionary',
'crhwiki',
'crwiki',
'crwikiquote',
'crwiktionary',
'csbwiki',
'csbwiktionary',
'cswiki',
'cswikibooks',
'cswikinews',
'cswikiquote',
'cswikisource',
'cswikiversity',
'cswikivoyage',
'cswiktionary',
'cuwiki',
'cvwiki',
'cvwikibooks',
'cywiki',
'cywikibooks',
'cywikiquote',
'cywikisource',
'cywiktionary',
'dagwiki',
'dawiki',
'dawikibooks',
'dawikiquote',
'dawikisource',
'dawiktionary',
'dewiki',
'dewikibooks',
'dewikinews',
'dewikiquote',
'dewikisource',
'dewikiversity',
'dewikivoyage',
'dewiktionary',
'dgawiki',
'dinwiki',
'diqwiki',
'diqwiktionary',
'dsbwiki',
'dtpwiki',
'dtywiki',
'dvwiki',
'dvwiktionary',
'dzwiki',
'dzwiktionary',
'eewiki',
'elwiki',
'elwikibooks',
'elwikinews',
'elwikiquote',
'elwikisource',
'elwikiversity',
'elwikivoyage',
'elwiktionary',
'emlwiki',
'enwiki',
'enwikibooks',
'enwikinews',
'enwikiquote',
'enwikisource',
'enwikiversity',
'enwikivoyage',
'enwiktionary',
'eowiki',
'eowikibooks',
'eowikinews',
'eowikiquote',
'eowikisource',
'eowikivoyage',
'eowiktionary',
'eswiki',
'eswikibooks',
'eswikinews',
'eswikiquote',
'eswikisource',
'eswikiversity',
'eswikivoyage',
'eswiktionary',
'etwiki',
'etwikibooks',
'etwikiquote',
'etwikisource',
'etwiktionary',
'euwiki',
'euwikibooks',
'euwikiquote',
'euwikisource',
'euwiktionary',
'extwiki',
'fatwiki',
'fawiki',
'fawikibooks',
'fawikinews',
'fawikiquote',
'fawikisource',
'fawikivoyage',
'fawiktionary',
'ffwiki',
'fiu_vrowiki',
'fiwiki',
'fiwikibooks',
'fiwikinews',
'fiwikiquote',
'fiwikisource',
'fiwikiversity',
'fiwikivoyage',
'fiwiktionary',
'fjwiki',
'fjwiktionary',
'fonwiki',
'foundationwiki',
'fowiki',
'fowikisource',
'fowiktionary',
'frpwiki',
'frrwiki',
'frwiki',
'frwikibooks',
'frwikinews',
'frwikiquote',
'frwikisource',
'frwikiversity',
'frwikivoyage',
'frwiktionary',
'furwiki',
'fywiki',
'fywikibooks',
'fywiktionary',
'gagwiki',
'ganwiki',
'gawiki',
'gawikibooks',
'gawikiquote',
'gawiktionary',
'gcrwiki',
'gdwiki',
'gdwiktionary',
'glkwiki',
'glwiki',
'glwikibooks',
'glwikiquote',
'glwikisource',
'glwiktionary',
'gnwiki',
'gnwikibooks',
'gnwiktionary',
'gomwiki',
'gomwiktionary',
'gorwiki',
'gorwikiquote',
'gorwiktionary',
'gotwiki',
'gotwikibooks',
'gpewiki',
'gucwiki',
'gurwiki',
'guwiki',
'guwikibooks',
'guwikiquote',
'guwikisource',
'guwiktionary',
'guwwiki',
'guwwikinews',
'guwwikiquote',
'guwwiktionary',
'gvwiki',
'gvwiktionary',
'hakwiki',
'hawiki',
'hawiktionary',
'hawwiki',
'hewiki',
'hewikibooks',
'hewikinews',
'hewikiquote',
'hewikisource',
'hewikivoyage',
'hewiktionary',
'hifwiki',
'hifwiktionary',
'hiwiki',
'hiwikibooks',
'hiwikiquote',
'hiwikisource',
'hiwikiversity',
'hiwikivoyage',
'hiwiktionary',
'howiki',
'hrwiki',
'hrwikibooks',
'hrwikiquote',
'hrwikisource',
'hrwiktionary',
'hsbwiki',
'hsbwiktionary',
'htwiki',
'htwikisource',
'huwiki',
'huwikibooks',
'huwikinews',
'huwikiquote',
'huwikisource',
'huwiktionary',
'hywiki',
'hywikibooks',
'hywikiquote',
'hywikisource',
'hywiktionary',
'hywwiki',
'hzwiki',
'iawiki',
'iawikibooks',
'iawiktionary',
'idwiki',
'idwikibooks',
'idwikiquote',
'idwikisource',
'idwiktionary',
'iewiki',
'iewikibooks',
'iewiktionary',
'iglwiki',
'igwiki',
'igwikiquote',
'igwiktionary',
'iiwiki',
'ikwiki',
'ikwiktionary',
'ilowiki',
'inhwiki',
'iowiki',
'iowiktionary',
'iswiki',
'iswikibooks',
'iswikiquote',
'iswikisource',
'iswiktionary',
'itwiki',
'itwikibooks',
'itwikinews',
'itwikiquote',
'itwikisource',
'itwikiversity',
'itwikivoyage',
'itwiktionary',
'iuwiki',
'iuwiktionary',
'jamwiki',
'jawiki',
'jawikibooks',
'jawikinews',
'jawikiquote',
'jawikisource',
'jawikiversity',
'jawikivoyage',
'jawiktionary',
'jbowiki',
'jbowiktionary',
'jvwiki',
'jvwikisource',
'jvwiktionary',
'kaawiki',
'kaawiktionary',
'kabwiki',
'kawiki',
'kawikibooks',
'kawikiquote',
'kawikisource',
'kawiktionary',
'kbdwiki',
'kbdwiktionary',
'kbpwiki',
'kcgwiki',
'kcgwiktionary',
'kgewiki',
'kgwiki',
'kiwiki',
'kjwiki',
'kkwiki',
'kkwikibooks',
'kkwikiquote',
'kkwiktionary',
'klwiki',
'klwiktionary',
'kmwiki',
'kmwikibooks',
'kmwiktionary',
'knwiki',
'knwikibooks',
'knwikiquote',
'knwikisource',
'knwiktionary',
'koiwiki',
'kowiki',
'kowikibooks',
'kowikinews',
'kowikiquote',
'kowikisource',
'kowikiversity',
'kowiktionary',
'krcwiki',
'krwiki',
'krwikiquote',
'kshwiki',
'kswiki',
'kswikibooks',
'kswikiquote',
'kswiktionary',
'kuswiki',
'kuwiki',
'kuwikibooks',
'kuwikiquote',
'kuwiktionary',
'kvwiki',
'kwwiki',
'kwwikiquote',
'kwwiktionary',
'kywiki',
'kywikibooks',
'kywikiquote',
'kywiktionary',
'ladwiki',
'lawiki',
'lawikibooks',
'lawikiquote',
'lawikisource',
'lawiktionary',
'lbewiki',
'lbwiki',
'lbwikibooks',
'lbwikiquote',
'lbwiktionary',
'lezwiki',
'lfnwiki',
'lgwiki',
'lijwiki',
'lijwikisource',
'liwiki',
'liwikibooks',
'liwikinews',
'liwikiquote',
'liwikisource',
'liwiktionary',
'lldwiki',
'lmowiki',
'lmowiktionary',
'lnwiki',
'lnwikibooks',
'lnwiktionary',
'lowiki',
'lowiktionary',
'lrcwiki',
'ltgwiki',
'ltwiki',
'ltwikibooks',
'ltwikiquote',
'ltwikisource',
'ltwiktionary',
'lvwiki',
'lvwikibooks',
'lvwiktionary',
'madwiki',
'madwiktionary',
'maiwiki',
'map_bmswiki',
'mdfwiki',
'mediawikiwiki',
'metawiki',
'mgwiki',
'mgwikibooks',
'mgwiktionary',
'mhrwiki',
'mhwiki',
'mhwiktionary',
'minwiki',
'minwiktionary',
'miwiki',
'miwikibooks',
'miwiktionary',
'mkwiki',
'mkwikibooks',
'mkwikisource',
'mkwiktionary',
'mlwiki',
'mlwikibooks',
'mlwikiquote',
'mlwikisource',
'mlwiktionary',
'mniwiki',
'mniwiktionary',
'mnwiki',
'mnwikibooks',
'mnwiktionary',
'mnwwiki',
'mnwwiktionary',
'moswiki',
'mowiki',
'mowiktionary',
'mrjwiki',
'mrwiki',
'mrwikibooks',
'mrwikiquote',
'mrwikisource',
'mrwiktionary',
'mswiki',
'mswikibooks',
'mswikisource',
'mswiktionary',
'mtwiki',
'mtwiktionary',
'muswiki',
'mwlwiki',
'myvwiki',
'mywiki',
'mywikibooks',
'mywikisource',
'mywiktionary',
'mznwiki',
'nahwiki',
'nahwikibooks',
'nahwiktionary',
'napwiki',
'napwikisource',
'nawiki',
'nawikibooks',
'nawikiquote',
'nawiktionary',
'nds_nlwiki',
'ndswiki',
'ndswikibooks',
'ndswikiquote',
'ndswiktionary',
'newiki',
'newikibooks',
'newiktionary',
'newwiki',
'ngwiki',
'niawiki',
'niawiktionary',
'nlwiki',
'nlwikibooks',
'nlwikinews',
'nlwikiquote',
'nlwikisource',
'nlwikivoyage',
'nlwiktionary',
'nnwiki',
'nnwikiquote',
'nnwiktionary',
'novwiki',
'nowiki',
'nowikibooks',
'nowikinews',
'nowikiquote',
'nowikisource',
'nowiktionary',
'nqowiki',
'nrmwiki',
'nsowiki',
'nvwiki',
'nywiki',
'ocwiki',
'ocwikibooks',
'ocwiktionary',
'olowiki',
'omwiki',
'omwiktionary',
'orwiki',
'orwikisource',
'orwiktionary',
'oswiki',
'outreachwiki',
'pagwiki',
'pamwiki',
'papwiki',
'pawiki',
'pawikibooks',
'pawikisource',
'pawiktionary',
'pcdwiki',
'pcmwiki',
'pdcwiki',
'pflwiki',
'pihwiki',
'piwiki',
'piwiktionary',
'plwiki',
'plwikibooks',
'plwikinews',
'plwikiquote',
'plwikisource',
'plwikivoyage',
'plwiktionary',
'pmswiki',
'pmswikisource',
'pnbwiki',
'pnbwiktionary',
'pntwiki',
'pswiki',
'pswikibooks',
'pswikivoyage',
'pswiktionary',
'ptwiki',
'ptwikibooks',
'ptwikinews',
'ptwikiquote',
'ptwikisource',
'ptwikiversity',
'ptwikivoyage',
'ptwiktionary',
'pwnwiki',
'quwiki',
'quwikibooks',
'quwikiquote',
'quwiktionary',
'rmwiki',
'rmwikibooks',
'rmwiktionary',
'rmywiki',
'rnwiki',
'rnwiktionary',
'roa_rupwiki',
'roa_rupwiktionary',
'roa_tarawiki',
'rowiki',
'rowikibooks',
'rowikinews',
'rowikiquote',
'rowikisource',
'rowikivoyage',
'rowiktionary',
'ruewiki',
'ruwiki',
'ruwikibooks',
'ruwikinews',
'ruwikiquote',
'ruwikisource',
'ruwikiversity',
'ruwikivoyage',
'ruwiktionary',
'rwwiki',
'rwwiktionary',
'sahwiki',
'sahwikiquote',
'sahwikisource',
'satwiki',
'sawiki',
'sawikibooks',
'sawikiquote',
'sawikisource',
'sawiktionary',
'scnwiki',
'scnwiktionary',
'scowiki',
'scwiki',
'scwiktionary',
'sdwiki',
'sdwikinews',
'sdwiktionary',
'sewiki',
'sewikibooks',
'sgwiki',
'sgwiktionary',
'shiwiki',
'shnwiki',
'shnwikibooks',
'shnwikinews',
'shnwikivoyage',
'shnwiktionary',
'shwiki',
'shwiktionary',
'shywiktionary',
'simplewiki',
'simplewikibooks',
'simplewikiquote',
'simplewiktionary',
'siwiki',
'siwikibooks',
'siwiktionary',
'skrwiki',
'skrwiktionary',
'skwiki',
'skwikibooks',
'skwikiquote',
'skwikisource',
'skwiktionary',
'slwiki',
'slwikibooks',
'slwikiquote',
'slwikisource',
'slwikiversity',
'slwiktionary',
'smnwiki',
'smwiki',
'smwiktionary',
'snwiki',
'snwiktionary',
'sourceswiki',
'sowiki',
'sowiktionary',
'specieswiki',
'sqwiki',
'sqwikibooks',
'sqwikinews',
'sqwikiquote',
'sqwiktionary',
'srnwiki',
'srwiki',
'srwikibooks',
'srwikinews',
'srwikiquote',
'srwikisource',
'srwiktionary',
'sswiki',
'sswiktionary',
'stqwiki',
'stwiki',
'stwiktionary',
'suwiki',
'suwikibooks',
'suwikiquote',
'suwikisource',
'suwiktionary',
'svwiki',
'svwikibooks',
'svwikinews',
'svwikiquote',
'svwikisource',
'svwikiversity',
'svwikivoyage',
'svwiktionary',
'swwiki',
'swwikibooks',
'swwiktionary',
'szlwiki',
'szywiki',
'tawiki',
'tawikibooks',
'tawikinews',
'tawikiquote',
'tawikisource',
'tawiktionary',
'taywiki',
'tcywiki',
'tetwiki',
'tewiki',
'tewikibooks',
'tewikiquote',
'tewikisource',
'tewiktionary',
'tgwiki',
'tgwikibooks',
'tgwiktionary',
'thwiki',
'thwikibooks',
'thwikinews',
'thwikiquote',
'thwikisource',
'thwiktionary',
'tiwiki',
'tiwiktionary',
'tkwiki',
'tkwikibooks',
'tkwikiquote',
'tkwiktionary',
'tlwiki',
'tlwikibooks',
'tlwikiquote',
'tlwiktionary',
'tlywiki',
'tnwiki',
'tnwiktionary',
'towiki',
'towiktionary',
'tpiwiki',
'tpiwiktionary',
'trvwiki',
'trwiki',
'trwikibooks',
'trwikinews',
'trwikiquote',
'trwikisource',
'trwikivoyage',
'trwiktionary',
'tswiki',
'tswiktionary',
'ttwiki',
'ttwikibooks',
'ttwikiquote',
'ttwiktionary',
'tumwiki',
'twwiki',
'twwiktionary',
'tyvwiki',
'tywiki',
'udmwiki',
'ugwiki',
'ugwikibooks',
'ugwikiquote',
'ugwiktionary',
'ukwiki',
'ukwikibooks',
'ukwikinews',
'ukwikiquote',
'ukwikisource',
'ukwikivoyage',
'ukwiktionary',
'urwiki',
'urwikibooks',
'urwikiquote',
'urwiktionary',
'uzwiki',
'uzwikibooks',
'uzwikiquote',
'uzwiktionary',
'vecwiki',
'vecwikisource',
'vecwiktionary',
'vepwiki',
'vewiki',
'viwiki',
'viwikibooks',
'viwikiquote',
'viwikisource',
'viwikivoyage',
'viwiktionary',
'vlswiki',
'vowiki',
'vowikibooks',
'vowikiquote',
'vowiktionary',
'warwiki',
'wawiki',
'wawikibooks',
'wawikisource',
'wawiktionary',
'wikidatawiki',
'wikifunctionswiki',
'wikimaniawiki',
'wowiki',
'wowikiquote',
'wowiktionary',
'wuuwiki',
'xalwiki',
'xhwiki',
'xhwikibooks',
'xhwiktionary',
'xmfwiki',
'yiwiki',
'yiwikisource',
'yiwiktionary',
'yowiki',
'yowikibooks',
'yowiktionary',
'yuewiktionary',
'zawiki',
'zawikibooks',
'zawikiquote',
'zawiktionary',
'zeawiki',
'zghwiki',
'zh_classicalwiki',
'zh_min_nanwiki',
'zh_min_nanwikibooks',
'zh_min_nanwikiquote',
'zh_min_nanwikisource',
'zh_min_nanwiktionary',
'zh_yuewiki',
'zhwiki',
'zhwikibooks',
'zhwikinews',
'zhwikiquote',
'zhwikisource',
'zhwikiversity',
'zhwikivoyage',
'zhwiktionary',
'zuwiki',
'zuwikibooks',
'zuwiktionary',
];
const wikimediaLanguageCodes = [
'aa',
'aae',
'ab',
'abs',
'ace',
'acf',
'acm',
'ady',
'ady-cyrl',
'aeb',
'aeb-arab',
'aeb-latn',
'af',
'agq',
'ak',
'aln',
'als',
'alt',
'am',
'ami',
'an',
'ang',
'ann',
'anp',
'apc',
'ar',
'arc',
'arn',
'arq',
'ary',
'arz',
'as',
'ase',
'ast',
'atj',
'av',
'avk',
'awa',
'ay',
'az',
'azb',
'ba',
'bag',
'ban',
'ban-bali',
'bar',
'bas',
'bat-smg',
'bax',
'bbc',
'bbc-latn',
'bbj',
'bcc',
'bci',
'bcl',
'bdr',
'be',
'be-tarask',
'be-x-old',
'bew',
'bfd',
'bg',
'bgc',
'bgn',
'bh',
'bho',
'bi',
'bjn',
'bkc',
'bkh',
'bkm',
'blk',
'bm',
'bn',
'bo',
'bpy',
'bqi',
'bqz',
'br',
'brh',
'bs',
'btm',
'bto',
'bug',
'bxr',
'byv',
'ca',
'cak',
'cal',
'cbk-zam',
'ccp',
'cdo',
'ce',
'ceb',
'ch',
'chn',
'cho',
'chr',
'chy',
'ckb',
'cnh',
'co',
'cps',
'cpx',
'cpx-hans',
'cpx-hant',
'cpx-latn',
'cr',
'crh',
'crh-cyrl',
'crh-latn',
'crh-ro',
'cs',
'csb',
'cu',
'cv',
'cy',
'da',
'dag',
'de',
'de-at',
'de-ch',
'de-formal',
'dga',
'din',
'diq',
'dsb',
'dtp',
'dty',
'dua',
'dv',
'dz',
'ee',
'efi',
'egl',
'el',
'eml',
'en',
'en-ca',
'en-gb',
'en-us',
'eo',
'es',
'es-419',
'es-formal',
'et',
'eto',
'etu',
'eu',
'ewo',
'ext',
'fa',
'fat',
'ff',
'fi',
'fit',
'fiu-vro',
'fj',
'fkv',
'fmp',
'fo',
'fon',
'fr',
'frc',
'frp',
'frr',
'fur',
'fy',
'ga',
'gaa',
'gag',
'gan',
'gan-hans',
'gan-hant',
'gcf',
'gcr',
'gd',
'gl',
'gld',
'glk',
'gn',
'gom',
'gom-deva',
'gom-latn',
'gor',
'got',
'gpe',
'grc',
'gsw',
'gu',
'guc',
'gur',
'guw',
'gv',
'gya',
'ha',
'hak',
'hak-hans',
'hak-hant',
'hak-latn',
'haw',
'he',
'hi',
'hif',
'hif-latn',
'hil',
'hno',
'ho',
'hr',
'hrx',
'hsb',
'hsn',
'ht',
'hu',
'hu-formal',
'hy',
'hyw',
'hz',
'ia',
'iba',
'ibb',
'id',
'ie',
'ig',
'igl',
'ii',
'ik',
'ike-cans',
'ike-latn',
'ilo',
'inh',
'io',
'is',
'isu',
'isv-cyrl',
'isv-latn',
'it',
'iu',
'ja',
'jam',
'jbo',
'jut',
'jv',
'ka',
'kaa',
'kab',
'kai',
'kbd',
'kbd-cyrl',
'kbp',
'kcg',
'kea',
'ker',
'kg',
'kge',
'khw',
'ki',
'kiu',
'kj',
'kjh',
'kjp',
'kk',
'kk-arab',
'kk-cn',
'kk-cyrl',
'kk-kz',
'kk-latn',
'kk-tr',
'kl',
'km',
'kn',
'ko',
'ko-kp',
'koi',
'kr',
'krc',
'kri',
'krj',
'krl',
'ks',
'ks-arab',
'ks-deva',
'ksf',
'ksh',
'ksw',
'ku',
'ku-arab',
'ku-latn',
'kum',
'kus',
'kv',
'kw',
'ky',
'la',
'lad',
'lb',
'lbe',
'lem',
'lez',
'lfn',
'lg',
'li',
'lij',
'liv',
'lki',
'lld',
'lmo',
'ln',
'lns',
'lo',
'loz',
'lrc',
'lt',
'ltg',
'lua',
'lus',
'luz',
'lv',
'lzh',
'lzz',
'mad',
'mag',
'mai',
'map-bms',
'mcn',
'mcp',
'mdf',
'mg',
'mh',
'mhr',
'mi',
'min',
'mk',
'ml',
'mn',
'mnc',
'mnc-latn',
'mnc-mong',
'mni',
'mnw',
'mo',
'mos',
'mr',
'mrh',
'mrj',
'ms',
'ms-arab',
'mt',
'mua',
'mui',
'mul',
'mus',
'mwl',
'my',
'myv',
'mzn',
'na',
'nah',
'nan',
'nan-hani',
'nan-hant',
'nan-latn-pehoeji',
'nan-latn-tailo',
'nap',
'nb',
'nds',
'nds-nl',
'ne',
'new',
'ng',
'nge',
'nia',
'nit',
'niu',
'nl',
'nl-informal',
'nla',
'nmg',
'nmz',
'nn',
'nnh',
'nnz',
'no',
'nod',
'nog',
'nov',
'nqo',
'nrm',
'nso',
'nup',
'nv',
'ny',
'nyn',
'nyo',
'nys',
'oc',
'ojb',
'olo',
'om',
'or',
'os',
'osa-latn',
'ota',
'pa',
'pag',
'pam',
'pap',
'pap-aw',
'pcd',
'pcm',
'pdc',
'pdt',
'pfl',
'pi',
'pih',
'pl',
'pms',
'pnb',
'pnt',
'prg',
'ps',
'pt',
'pt-br',
'pwn',
'qu',
'quc',
'qug',
'rgn',
'rif',
'rki',
'rm',
'rmc',
'rmf',
'rmy',
'rn',
'ro',
'roa-rup',
'roa-tara',
'rsk',
'ru',
'rue',
'rup',
'ruq',
'ruq-cyrl',
'ruq-latn',
'rut',
'rw',
'rwr',
'ryu',
'sa',
'sah',
'sat',
'sc',
'scn',
'sco',
'sd',
'sdc',
'sdh',
'se',
'se-fi',
'se-no',
'se-se',
'sei',
'ses',
'sg',
'sgs',
'sh',
'sh-cyrl',
'sh-latn',
'shi',
'shi-latn',
'shi-tfng',
'shn',
'shy',
'shy-latn',
'si',
'simple',
'sjd',
'sje',
'sju',
'sk',
'skr',
'skr-arab',
'sl',
'sli',
'sm',
'sma',
'smj',
'smn',
'sms',
'sn',
'so',
'sq',
'sr',
'sr-ec',
'sr-el',
'srn',
'sro',
'srq',
'ss',
'st',
'stq',
'sty',
'su',
'sv',
'sw',
'syl',
'szl',
'szy',
'ta',
'tay',
'tcy',
'tdd',
'te',
'tet',
'tg',
'tg-cyrl',
'tg-latn',
'th',
'ti',
'tig',
'tk',
'tl',
'tly',
'tly-cyrl',
'tn',
'to',
'tok',
'tpi',
'tpv',
'tr',
'tru',
'trv',
'ts',
'tt',
'tt-cyrl',
'tt-latn',
'ttj',
'tum',
'tvu',
'tw',
'ty',
'tyv',
'tzm',
'udm',
'ug',
'ug-arab',
'ug-latn',
'uk',
'ur',
'uz',
'uz-cyrl',
'uz-latn',
've',
'vec',
'vep',
'vi',
'vls',
'vmf',
'vmw',
'vo',
'vot',
'vro',
'vut',
'wa',
'wal',
'war',
'wes',
'wls',
'wo',
'wuu',
'wuu-hans',
'wuu-hant',
'wya',
'xal',
'xh',
'xmf',
'xsy',
'yas',
'yat',
'yav',
'ybb',
'yi',
'yo',
'yrl',
'yue',
'yue-hans',
'yue-hant',
'za',
'zea',
'zgh',
'zgh-latn',
'zh',
'zh-classical',
'zh-cn',
'zh-hans',
'zh-hant',
'zh-hk',
'zh-min-nan',
'zh-mo',
'zh-my',
'zh-sg',
'zh-tw',
'zh-yue',
'zu',
];
const wikidataBase = 'https://www.wikidata.org/wiki/';
function getSitelinkUrl({ site, title }) {
rejectObsoleteInterface(arguments);
if (!site)
throw new Error('missing a site');
if (!title)
throw new Error('missing a title');
if (isAKey(siteUrlBuilders, site)) {
return siteUrlBuilders[site](title);
}
const shortSiteKey = site.replace(/wiki$/, '');
if (isAKey(siteUrlBuilders, shortSiteKey)) {
return siteUrlBuilders[shortSiteKey](title);
}
const { lang, project } = getSitelinkData(site);
title = fixedEncodeURIComponent(replaceSpaceByUnderscores(title));
return `https://${lang}.${project}.org/wiki/${title}`;
}
const wikimediaSite = (subdomain) => (title) => `https://${subdomain}.wikimedia.org/wiki/${title}`;
const siteUrlBuilders = {
commons: wikimediaSite('commons'),
foundation: wikimediaSite('foundation'),
mediawiki: title => `https://www.mediawiki.org/wiki/${title}`,
meta: wikimediaSite('meta'),
outreach: wikimediaSite('outreach'),
sources: title => `https://wikisource.org/wiki/${title}`,
species: wikimediaSite('species'),
wikidata: entityId => {
const prefix = prefixByEntityLetter[entityId[0]];
let title = prefix ? `${prefix}:${entityId}` : entityId;
// Required for forms and senses
title = title.replace('-', '#');
return `${wikidataBase}${title}`;
},
wikifunctions: wikimediaSite('wikifunctions'),
wikimania: wikimediaSite('wikimania'),
};
const prefixByEntityLetter = {
E: 'EntitySchema',
L: 'Lexeme',
P: 'Property',
};
const sitelinkUrlPattern = /^https?:\/\/([\w-]{2,10})\.(\w+)\.org\/\w+\/(.*)/;
function getSitelinkData(site) {
if (site.startsWith('http')) {
const url = site;
const matchData = url.match(sitelinkUrlPattern);
if (!matchData)
throw new Error(`invalid sitelink url: ${url}`);
let [lang, project, title] = matchData.slice(1);
title = decodeURIComponent(title);
if (lang === 'commons') {
return { lang: 'en', project: 'commons', key: 'commons', title, url };
}
if (!isOfType(projectNames, project)) {
throw new Error(`project is unknown: ${project}`);
}
// Known case: wikidata, mediawiki
if (lang === 'www') {
return { lang: 'en', project, key: project, title, url };
}
// Support multi-parts language codes, such as be_x_old
const sitelang = lang.replace(/-/g, '_');
const key = `${sitelang}${project}`.replace('wikipedia', 'wiki');
return { lang, project, key, title, url };
}
else {
if (isAKey(specialSites, site)) {
const project = specialSites[site];
return { lang: 'en', project, key: site };
}
if (!isOfType(sites,