@whiteslove/parsing-lexicon 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/geo.js +1 -1
- package/src/geography-display.d.ts +28 -2
- package/src/geography-display.js +109 -1
package/package.json
CHANGED
package/src/geo.js
CHANGED
|
@@ -160,7 +160,7 @@ export const TASHKENT_AREAS = Object.freeze({
|
|
|
160
160
|
]),
|
|
161
161
|
Yunusabad: Object.freeze([
|
|
162
162
|
area('Kashgar', ['кашгар', 'қашқар', 'qashqar', 'kashgar', 'ц 4', 'c 4']), area('Qiyot', ['киёт', 'кият', 'қиёт', 'qiyot', 'kiyot', 'markaz 5', 's 5']),
|
|
163
|
-
area('Minor', ['минор', 'minor', 'ц 6', 'c 6']), area('
|
|
163
|
+
area('Minor', ['минор', 'minor', 'ц 6', 'c 6']), area('ToshGRES', ['ташгрэс', 'tashgres', 'TashGRES', 'toshgres']), area('Manzara', ['манзара', 'manzara']),
|
|
164
164
|
]),
|
|
165
165
|
Yakkasaray: Object.freeze([
|
|
166
166
|
area('Bashlyk', ['башлык', 'бошлиқ', 'boshliq', 'bashlyk']), area('Kushbegi', ['кушбеги', 'қушбеги', 'qushbegi', 'kushbegi']),
|
|
@@ -1,13 +1,39 @@
|
|
|
1
|
-
export type GeographyDisplayKind =
|
|
1
|
+
export type GeographyDisplayKind =
|
|
2
|
+
| 'country'
|
|
3
|
+
| 'region'
|
|
4
|
+
| 'city'
|
|
5
|
+
| 'district'
|
|
6
|
+
| 'microdistrict'
|
|
7
|
+
| 'mahalla'
|
|
8
|
+
| 'local_area'
|
|
9
|
+
| 'suburb'
|
|
10
|
+
| 'settlement'
|
|
11
|
+
| 'development_area'
|
|
12
|
+
| 'residential_complex'
|
|
13
|
+
| 'metro'
|
|
14
|
+
| 'street'
|
|
15
|
+
| 'poi'
|
|
16
|
+
| 'any';
|
|
17
|
+
|
|
18
|
+
export type GeographyDisplayContext = Readonly<{
|
|
19
|
+
country?: string | null;
|
|
20
|
+
city?: string | null;
|
|
21
|
+
}>;
|
|
2
22
|
|
|
3
23
|
export const GEOGRAPHY_DISPLAY_NAMES: Readonly<Record<string, Readonly<{
|
|
4
24
|
country: Readonly<Record<string, string>>;
|
|
5
25
|
city: Readonly<Record<string, string>>;
|
|
6
26
|
region?: Readonly<Record<string, string>>;
|
|
7
27
|
district: Readonly<Record<string, string>>;
|
|
28
|
+
microdistrict: Readonly<Record<string, string>>;
|
|
8
29
|
metro: Readonly<Record<string, string>>;
|
|
9
30
|
metroAlias: Readonly<Record<string, string>>;
|
|
10
31
|
}>>>;
|
|
11
32
|
|
|
12
|
-
export function geographyDisplayName(
|
|
33
|
+
export function geographyDisplayName(
|
|
34
|
+
value: unknown,
|
|
35
|
+
locale?: string,
|
|
36
|
+
kind?: GeographyDisplayKind,
|
|
37
|
+
context?: GeographyDisplayContext | null,
|
|
38
|
+
): string;
|
|
13
39
|
export function geographyMetroLabelWithAlias(value: unknown, locale?: string): string;
|
package/src/geography-display.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import { COUNTRIES, canonicalCountryCode, countryByCode } from './countries.js';
|
|
2
2
|
import { CITIES, REGIONS } from './geography.js';
|
|
3
|
+
import { dictionaryFor } from './locations.js';
|
|
4
|
+
import { aliasesOf } from './normalization.js';
|
|
3
5
|
|
|
4
6
|
// Centralized presentation names for canonical geography values.
|
|
5
7
|
// Consumers must not maintain their own country/city/district/metro display dictionaries.
|
|
6
8
|
|
|
7
9
|
const EMPTY_DISPLAY_NAMES = Object.freeze({});
|
|
8
10
|
|
|
11
|
+
const LOCATION_KIND_KEYS = Object.freeze({
|
|
12
|
+
district: Object.freeze(['districts']),
|
|
13
|
+
microdistrict: Object.freeze(['microdistricts']),
|
|
14
|
+
mahalla: Object.freeze(['mahallas']),
|
|
15
|
+
local_area: Object.freeze(['localAreas']),
|
|
16
|
+
suburb: Object.freeze(['suburbs']),
|
|
17
|
+
settlement: Object.freeze(['settlements']),
|
|
18
|
+
development_area: Object.freeze(['developmentAreas']),
|
|
19
|
+
residential_complex: Object.freeze(['residentialComplexes']),
|
|
20
|
+
metro: Object.freeze(['metro']),
|
|
21
|
+
street: Object.freeze(['streets']),
|
|
22
|
+
poi: Object.freeze(['landmarks', 'pois']),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const CYRILLIC_RE = /[А-ЯЁ]/iu;
|
|
26
|
+
const UZBEK_CYRILLIC_RE = /[ЎҚҒҲўқғҳ]/u;
|
|
27
|
+
const LATIN_RE = /[A-Z]/iu;
|
|
28
|
+
const UZBEK_LATIN_MARKER_RE = /(?:[ʻ‘’ʼ']|\b(?:ko[ʻ‘’ʼ']?chasi|shoh\s+ko[ʻ‘’ʼ']?chasi|bozori|bog[ʻ‘’ʼ']?i|maydoni|masjidi|vokzali|mahallasi|mavzesi|dahasi|massivi|savdo\s+markazi|toshkent|yunusobod|chilonzor|olmazor|o[ʻ‘’ʼ']?zbekiston)\b)/iu;
|
|
29
|
+
const ENGLISH_GENERIC_RE = /\b(?:street|avenue|square|park|bazaar|market|mall|mosque|railway|station|residence|city|garden|museum|theatre|theater|airport|university|hospital|school|district|microdistrict)\b/iu;
|
|
30
|
+
const RUSSIAN_GENERIC_RE = /\b(?:улица|проспект|площадь|сквер|рынок|базар|парк|мечеть|вокзал|аэропорт|университет|больница|школа|район|массив|махалл|жилой|жк|центр)\b/iu;
|
|
31
|
+
|
|
9
32
|
function countryDisplayNames(locale) {
|
|
10
33
|
return Object.freeze(Object.fromEntries(COUNTRIES.map((item) => [
|
|
11
34
|
item.code,
|
|
@@ -22,6 +45,20 @@ export const GEOGRAPHY_DISPLAY_NAMES = Object.freeze({
|
|
|
22
45
|
metro: EMPTY_DISPLAY_NAMES,
|
|
23
46
|
metroAlias: EMPTY_DISPLAY_NAMES,
|
|
24
47
|
}),
|
|
48
|
+
uz: Object.freeze({
|
|
49
|
+
country: countryDisplayNames('uz'),
|
|
50
|
+
city: Object.freeze({
|
|
51
|
+
Tashkent: 'Toshkent',
|
|
52
|
+
}),
|
|
53
|
+
district: Object.freeze({
|
|
54
|
+
Chilanzar: 'Chilonzor', Yunusabad: 'Yunusobod', 'Mirzo Ulugbek': 'Mirzo Ulug‘bek',
|
|
55
|
+
Yakkasaray: 'Yakkasaroy', Shaykhantahur: 'Shayxontohur', Yashnobod: 'Yashnobod', Sergeli: 'Sergeli',
|
|
56
|
+
Uchtepa: 'Uchtepa', Mirobod: 'Mirobod', Bektemir: 'Bektemir', Almazar: 'Olmazor', Yangihayot: 'Yangihayot',
|
|
57
|
+
}),
|
|
58
|
+
microdistrict: EMPTY_DISPLAY_NAMES,
|
|
59
|
+
metro: EMPTY_DISPLAY_NAMES,
|
|
60
|
+
metroAlias: EMPTY_DISPLAY_NAMES,
|
|
61
|
+
}),
|
|
25
62
|
ru: Object.freeze({
|
|
26
63
|
country: countryDisplayNames('ru'),
|
|
27
64
|
city: Object.freeze({
|
|
@@ -131,7 +168,70 @@ function countryEntity(value) {
|
|
|
131
168
|
return code ? countryByCode(code) : null;
|
|
132
169
|
}
|
|
133
170
|
|
|
134
|
-
|
|
171
|
+
function locationEntryFor(value, kind, context) {
|
|
172
|
+
const keys = LOCATION_KIND_KEYS[kind];
|
|
173
|
+
const country = canonicalCountryCode(context?.country);
|
|
174
|
+
const city = String(context?.city || '').trim();
|
|
175
|
+
if (!keys || !country || !city) return null;
|
|
176
|
+
const dictionary = dictionaryFor(country, city);
|
|
177
|
+
if (!dictionary) return null;
|
|
178
|
+
for (const key of keys) {
|
|
179
|
+
const entry = (dictionary[key] || []).find((item) => (item.canonical || item.name) === value);
|
|
180
|
+
if (entry) return entry;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function bestAlias(aliases, score) {
|
|
186
|
+
let best = null;
|
|
187
|
+
let bestScore = -Infinity;
|
|
188
|
+
for (const alias of aliases) {
|
|
189
|
+
const value = String(alias || '').trim();
|
|
190
|
+
if (!value) continue;
|
|
191
|
+
const valueScore = score(value);
|
|
192
|
+
if (valueScore > bestScore || (valueScore === bestScore && best && value.length < best.length)) {
|
|
193
|
+
best = value;
|
|
194
|
+
bestScore = valueScore;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return bestScore > 0 ? best : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function locationAliasLabel(entry, locale) {
|
|
201
|
+
if (!entry) return null;
|
|
202
|
+
const language = languageKey(locale);
|
|
203
|
+
const explicit = entry.labels?.[language];
|
|
204
|
+
if (explicit) return explicit;
|
|
205
|
+
const canonical = entry.canonical || entry.name || null;
|
|
206
|
+
if (language === 'en') return entry.labels?.en || canonical;
|
|
207
|
+
|
|
208
|
+
const aliases = aliasesOf(entry);
|
|
209
|
+
if (language === 'ru') {
|
|
210
|
+
return bestAlias(aliases, (alias) => {
|
|
211
|
+
if (!CYRILLIC_RE.test(alias)) return -10;
|
|
212
|
+
let score = 5;
|
|
213
|
+
if (RUSSIAN_GENERIC_RE.test(alias)) score += 8;
|
|
214
|
+
if (UZBEK_CYRILLIC_RE.test(alias)) score -= 4;
|
|
215
|
+
return score;
|
|
216
|
+
}) || canonical;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (language === 'uz') {
|
|
220
|
+
return bestAlias(aliases, (alias) => {
|
|
221
|
+
let score = 0;
|
|
222
|
+
if (LATIN_RE.test(alias)) score += 3;
|
|
223
|
+
if (UZBEK_LATIN_MARKER_RE.test(alias)) score += 10;
|
|
224
|
+
if (UZBEK_CYRILLIC_RE.test(alias)) score += 6;
|
|
225
|
+
if (CYRILLIC_RE.test(alias) && !UZBEK_CYRILLIC_RE.test(alias)) score -= 3;
|
|
226
|
+
if (ENGLISH_GENERIC_RE.test(alias)) score -= 8;
|
|
227
|
+
return score;
|
|
228
|
+
}) || canonical;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return canonical;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function geographyDisplayName(value, locale = 'en', kind = 'any', context = null) {
|
|
135
235
|
const text = String(value || '').trim();
|
|
136
236
|
if (!text) return '';
|
|
137
237
|
const language = languageKey(locale);
|
|
@@ -143,9 +243,17 @@ export function geographyDisplayName(value, locale = 'en', kind = 'any') {
|
|
|
143
243
|
}
|
|
144
244
|
if (kind === 'city') return tables?.city?.[text] || canonicalEntityLabel(CITIES, text, locale) || text;
|
|
145
245
|
if (kind === 'region') return tables?.region?.[text] || canonicalEntityLabel(REGIONS, text, locale) || text;
|
|
246
|
+
|
|
247
|
+
const localEntry = locationEntryFor(text, kind, context);
|
|
248
|
+
if (localEntry) {
|
|
249
|
+
const localized = locationAliasLabel(localEntry, locale);
|
|
250
|
+
if (localized) return localized;
|
|
251
|
+
}
|
|
252
|
+
|
|
146
253
|
if (kind === 'district') return tables?.district?.[text] || text;
|
|
147
254
|
if (kind === 'microdistrict') return tables?.microdistrict?.[text] || numberedMicrodistrictDisplayName(text, locale) || text;
|
|
148
255
|
if (kind === 'metro') return tables?.metro?.[text] || text;
|
|
256
|
+
if (LOCATION_KIND_KEYS[kind]) return text;
|
|
149
257
|
return tables?.country?.[canonicalCountryCode(text)] || tables?.city?.[text] || tables?.region?.[text] || tables?.district?.[text] || tables?.microdistrict?.[text] || tables?.metro?.[text] || canonicalEntityLabel(CITIES, text, locale) || canonicalEntityLabel(REGIONS, text, locale) || preferredEntityLabel(countryEntity(text), locale) || text;
|
|
150
258
|
}
|
|
151
259
|
|