Skip to content

Commit 0d8b0eb

Browse files
authored
Merge pull request #120 from EdJoPaTo/language-code
feat: LanguageCode = string
2 parents c8b7909 + c96d64b commit 0d8b0eb

21 files changed

+763
-511
lines changed

docs/general_helpers.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
- [Sitelink helpers](#sitelink-helpers)
2626
- [getSitelinkUrl](#getsitelinkurl)
2727
- [getSitelinkData](#getsitelinkdata)
28-
- [isSitelinkKey](#issitelinkkey)
28+
- [isSite](#issite)
2929
- [Wikibase Time converters](#wikibase-time-converters)
3030
- [wikibaseTimeToDateObject](#wikibasetimetodateobject)
3131
- [wikibaseTimeToEpochTime](#wikibasetimetoepochtime)
@@ -220,23 +220,23 @@ getSitelinkData('wikidatawiki')
220220
// => { lang: 'en', project: 'wikidata', key: 'wikidatawiki' }
221221
```
222222

223-
### isSitelinkKey
223+
### isSite
224224
```js
225-
import { isSitelinkKey } from 'wikibase-sdk'
225+
import { isSite } from 'wikibase-sdk'
226226

227-
isSitelinkKey('frwiki')
227+
isSite('frwiki')
228228
// => true
229-
isSitelinkKey('dewikiquote')
229+
isSite('dewikiquote')
230230
// => true
231-
isSitelinkKey('commons')
231+
isSite('commons')
232232
// => true
233233
// Accepting wikidata as a valid sitelink for convenience
234-
isSitelinkKey('wikidata')
234+
isSite('wikidata')
235235
// => true
236-
isSitelinkKey('frwikilinpinpin')
236+
isSite('frwikilinpinpin')
237237
// => false
238238
// /!\ langs are loosly validated
239-
isSitelinkKey('imaginarylangwiki')
239+
isSite('imaginarylangwiki')
240240
// => true
241241
```
242242

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"prepublishOnly": "git checkout main",
3636
"prepack": "npm run build && npm run lint && npm test",
3737
"postpublish": "./scripts/postpublish",
38-
"update-sitelinks-languages": "./scripts/sitelinks_languages/update_sitelinks_languages",
38+
"update-wikimedia-constants": "./scripts/update_wikimedia_constants.ts",
3939
"update-toc": "./scripts/update_toc",
4040
"watch": "tsc --watch"
4141
},

scripts/sitelinks_languages/generate_sitelinks_languages.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

scripts/sitelinks_languages/get_sitelinks_sites

Lines changed: 0 additions & 5 deletions
This file was deleted.

scripts/sitelinks_languages/update_sitelinks_languages

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env ts-node
2+
import { writeFileSync } from 'fs'
3+
import { uniq } from '../src/utils/utils.js'
4+
5+
interface Parameter {
6+
name: string
7+
type: string[]
8+
}
9+
10+
function stringifyArray (input: string[]) {
11+
return JSON.stringify(uniq(input), null, 2)
12+
// Prevent linting errors
13+
.replace(/"/g, '\'')
14+
.replace(/'\n/, '\',\n')
15+
}
16+
17+
function stringifySimpleRecord (input: Record<string, string>) {
18+
let output = '{\n'
19+
output += Object.entries(input).map(([ key, value ]) => ' ' + key + ': ' + '\'' + value + '\',\n').join('')
20+
output += '}'
21+
return output
22+
}
23+
24+
doit()
25+
async function doit () {
26+
const response = await fetch('https://www.wikidata.org/w/api.php?action=paraminfo&modules=wbgetentities&format=json')
27+
const data = await response.json()
28+
29+
const parameters = data.paraminfo.modules[0].parameters as Parameter[]
30+
31+
const sites = parameters.find(o => o.name === 'sites')?.type
32+
const languages = parameters.find(o => o.name === 'languages')?.type
33+
if (!sites || !languages) throw new Error('paraminfo format changed')
34+
35+
const specialSites: Record<string, string> = {}
36+
for (const site of sites) {
37+
const project = site.match(/^(.+)wiki$/)?.[1]
38+
if (!project) continue
39+
if (!languages.includes(project.replace(/_/g, '-'))) {
40+
specialSites[site] = project
41+
}
42+
}
43+
44+
const output = [
45+
"// Generated by 'npm run update-wikimedia-constants'",
46+
[
47+
'export type Site = typeof sites[number]',
48+
'export type WikimediaLanguageCode = typeof wikimediaLanguageCodes[number]',
49+
].join('\n'),
50+
'export const specialSites = ' + stringifySimpleRecord(specialSites) + ' as const',
51+
'export const sites = ' + stringifyArray(sites) + ' as const',
52+
'export const wikimediaLanguageCodes = ' + stringifyArray(languages) + ' as const',
53+
].join('\n\n') + '\n'
54+
55+
writeFileSync('./src/helpers/wikimedia_constants.ts', output, 'utf-8')
56+
}

src/helpers/simplify_text_attributes.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { typedEntries } from '../utils/utils.js'
2-
import type { WmLanguageCode } from '../types/options.js'
32
import type { Aliases, Descriptions, Glosses, Labels, Lemmas, Representations, SimplifiedAliases, SimplifiedDescriptions, SimplifiedGlosses, SimplifiedLabels, SimplifiedLemmas, SimplifiedRepresentations } from '../types/terms.js'
43

54
type InValue<T> = { readonly value: T }
65

7-
function singleValue<V> (data: Partial<Readonly<Record<WmLanguageCode, InValue<V>>>>) {
8-
const simplified: Partial<Record<WmLanguageCode, V>> = {}
6+
function singleValue<K extends string, V> (data: Partial<Readonly<Record<K, InValue<V>>>>) {
7+
const simplified: Partial<Record<K, V>> = {}
98
for (const [ lang, obj ] of typedEntries(data)) {
109
simplified[lang] = obj != null ? obj.value : null
1110
}
1211
return simplified
1312
}
1413

15-
function multiValue<V> (data: Partial<Readonly<Record<WmLanguageCode, ReadonlyArray<InValue<V>>>>>) {
16-
const simplified: Partial<Record<WmLanguageCode, readonly V[]>> = {}
14+
function multiValue<K extends string, V> (data: Partial<Readonly<Record<K, ReadonlyArray<InValue<V>>>>>) {
15+
const simplified: Partial<Record<K, readonly V[]>> = {}
1716
for (const [ lang, obj ] of typedEntries(data)) {
1817
simplified[lang] = obj != null ? obj.map(o => o.value) : []
1918
}

src/helpers/sitelinks.ts

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { fixedEncodeURIComponent, isAKey, isOfType, rejectObsoleteInterface, replaceSpaceByUnderscores } from '../utils/utils.js'
2-
import { languages } from './sitelinks_languages.js'
3-
import { specialSites } from './special_sites.js'
4-
import type { WmLanguageCode } from '../types/options.js'
5-
import type { Site } from '../types/sitelinks.js'
2+
import { specialSites, type Site, sites } from './wikimedia_constants.js'
3+
import type { LanguageCode } from '../types/options.js'
64
import type { Url } from '../utils/build_url.js'
75

86
const wikidataBase = 'https://www.wikidata.org/wiki/'
97

8+
type ValueOf<T> = T[keyof T]
9+
type SpecialSiteProjectName = ValueOf<typeof specialSites>
10+
1011
export interface GetSitelinkUrlOptions {
11-
site: Site
12+
site: Site | SpecialSiteProjectName
1213
title: string
1314
}
1415

@@ -34,20 +35,23 @@ export function getSitelinkUrl ({ site, title }: GetSitelinkUrlOptions): Url {
3435

3536
const wikimediaSite = (subdomain: string) => (title: string) => `https://${subdomain}.wikimedia.org/wiki/${title}`
3637

37-
const siteUrlBuilders = {
38+
const siteUrlBuilders: Readonly<Record<SpecialSiteProjectName, (s: string) => string>> = {
3839
commons: wikimediaSite('commons'),
39-
mediawiki: (title: string) => `https://www.mediawiki.org/wiki/${title}`,
40+
foundation: wikimediaSite('foundation'),
41+
mediawiki: title => `https://www.mediawiki.org/wiki/${title}`,
4042
meta: wikimediaSite('meta'),
43+
outreach: wikimediaSite('outreach'),
44+
sources: title => `https://wikisource.org/wiki/${title}`,
4145
species: wikimediaSite('species'),
42-
wikidata: (entityId: string) => {
46+
wikidata: entityId => {
4347
const prefix = prefixByEntityLetter[entityId[0]]
4448
let title = prefix ? `${prefix}:${entityId}` : entityId
4549
// Required for forms and senses
4650
title = title.replace('-', '#')
4751
return `${wikidataBase}${title}`
4852
},
4953
wikimania: wikimediaSite('wikimania'),
50-
} as const
54+
}
5155

5256
const prefixByEntityLetter = {
5357
E: 'EntitySchema',
@@ -58,7 +62,7 @@ const prefixByEntityLetter = {
5862
const sitelinkUrlPattern = /^https?:\/\/([\w-]{2,10})\.(\w+)\.org\/\w+\/(.*)/
5963

6064
export interface SitelinkData {
61-
lang: WmLanguageCode
65+
lang: LanguageCode
6266
project: Project
6367
key: string
6468
title?: string
@@ -72,59 +76,52 @@ export function getSitelinkData (site: Site | Url): SitelinkData {
7276
if (!matchData) throw new Error(`invalid sitelink url: ${url}`)
7377
let [ lang, project, title ] = matchData.slice(1)
7478
title = decodeURIComponent(title)
75-
let key: string
79+
if (lang === 'commons') {
80+
return { lang: 'en', project: 'commons', key: 'commons', title, url }
81+
}
82+
83+
if (!isOfType(projectNames, project)) {
84+
throw new Error(`project is unknown: ${project}`)
85+
}
86+
7687
// Known case: wikidata, mediawiki
7788
if (lang === 'www') {
78-
lang = 'en'
79-
key = project
80-
} else if (lang === 'commons') {
81-
lang = 'en'
82-
project = key = 'commons'
83-
} else {
84-
// Support multi-parts language codes, such as be_x_old
85-
lang = lang.replace(/-/g, '_')
86-
key = `${lang}${project}`.replace('wikipedia', 'wiki')
89+
return { lang: 'en', project, key: project, title, url }
8790
}
88-
// @ts-expect-error
91+
92+
// Support multi-parts language codes, such as be_x_old
93+
const sitelang = lang.replace(/-/g, '_')
94+
const key = `${sitelang}${project}`.replace('wikipedia', 'wiki')
8995
return { lang, project, key, title, url }
9096
} else {
91-
const key = site
9297
if (isAKey(specialSites, site)) {
9398
const project = specialSites[site]
94-
return { lang: 'en', project, key }
99+
return { lang: 'en', project, key: site }
95100
}
96101

97-
let [ lang, projectSuffix, rest ] = key.split('wik')
102+
if (!isOfType(sites, site)) {
103+
throw new Error(`site not found: ${site}. Updating wikibase-sdk to a more recent version might fix the issue.`)
104+
}
98105

99-
// Detecting cases like 'frwikiwiki' that would return [ 'fr', 'i', 'i' ]
100-
if (rest != null) throw new Error(`invalid sitelink key: ${key}`)
106+
let [ lang, projectSuffix, rest ] = site.split('wik')
101107

102-
if (!isOfType(languages, lang)) {
103-
throw new Error(`sitelink lang not found: ${lang}. Updating wikibase-sdk to a more recent version might fix the issue.`)
104-
}
108+
// Detecting cases like 'frwikiwiki' that would return [ 'fr', 'i', 'i' ]
109+
if (rest != null) throw new Error(`invalid sitelink key: ${site}`)
105110

106111
// Support keys such as be_x_oldwiki, which refers to be-x-old.wikipedia.org
107112
lang = lang.replace(/_/g, '-')
108113

109114
const project = projectsBySuffix[projectSuffix]
110115
if (!project) throw new Error(`sitelink project not found: ${project}`)
111116

112-
// @ts-expect-error
113-
return { lang, project, key }
117+
return { lang, project, key: site }
114118
}
115119
}
116120

117-
export const isSitelinkKey = (site: string): boolean => {
118-
try {
119-
// relies on getSitelinkData validation
120-
getSitelinkData(site)
121-
return true
122-
} catch (err) {
123-
return false
124-
}
125-
}
121+
export const isSite = (site: string): site is Site => isOfType(sites, site)
126122

127-
export const wikimediaLanguageCodes = languages
123+
/** @deprecated use isSite */
124+
export const isSitelinkKey = isSite
128125

129126
const projectsBySuffix = {
130127
i: 'wikipedia',

0 commit comments

Comments
 (0)