Skip to content

Commit 7106c1d

Browse files
committed
Implement a schema for the measurement field
1 parent 7c14759 commit 7106c1d

File tree

12 files changed

+260
-11
lines changed

12 files changed

+260
-11
lines changed

ā€Ž.github/workflows/build.ymlā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ jobs:
2424
with:
2525
node-version: ${{ matrix.node-version }}
2626
- run: npm install
27+
- run: npm run build
2728
- run: npm run lint
2829
- run: npm run test

ā€Ž.gitignoreā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ npm-debug.log
1010
/tests/workspace
1111

1212
transifex.auth
13+
schemas/generated

ā€ŽCHANGELOG.mdā€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
77
[#x]: https://github.com/ideditor/schema-builder/issues/x
88
-->
9+
# Unreleased
10+
11+
* :warning: Add _measurement_ field type ([#198], thanks [@k-yle])
12+
* Data consumers who don't support `type=measurement` should treat it as a `type=text`.
13+
14+
[#198]: https://github.com/ideditor/schema-builder/pull/198
915

1016
# 6.5.1
1117
##### 2024-Mar-14

ā€ŽREADME.mdā€Ž

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,7 @@ A string specifying the UI and behavior of the field. Must be one of the followi
473473

474474
* `access` - Block of dropdowns for defining the `access=*` tags on a highway
475475
* `address` - Block of text and dropdown fields for entering address information (localized for editing location)
476-
* `roadspeed` - Numeric text field for speed and dropdown for "mph" / "km/h", defaulting to the speed unit used for roads in the feature's region
477-
* `roadheight` - Numeric text field for height and dropdowns for "m" / "ft" and "in", defaulting to the height unit used for roads in the feature's region
476+
* `measurement` - Numeric text field with associated unit of measurement, such as inches or kilometers-per-hour. The field may have multiple units. See [#measurement](#measurement) for details.
478477
* `restrictions` - Graphical field for editing turn restrictions
479478
* `wikidata` - Search field for selecting a Wikidata entity
480479
* `wikipedia` - Block of fields for selecting a wiki language and Wikipedia page
@@ -740,6 +739,36 @@ Combo field types can accept key-label pairs in the `options` value of the `stri
740739

741740
An optional property to reference to the icons of another field, indicated by using that field's name contained in brackets, like `{field}`. This is for example useful when there are multiple variants of fields for the same tag, which should all use the same icons.
742741

742+
##### `measurement`
743+
744+
Used when `type = measurement`. Defines the unit of measurements that are supported by this field. For example:
745+
746+
```json
747+
{
748+
"key": "diameter",
749+
"type": "measurement",
750+
"measurement": {
751+
// The dimension being measured. This constrains the permitted units.
752+
// The ID id defined by CLDR.
753+
"dimension": "length",
754+
755+
"units": {
756+
// The key defines the ID of the of the unit, as defined by CLDR.
757+
// The values define the suffix used in the OSM tag value.
758+
// If there are multiple values in the array (such as "kW", "KW"),
759+
// then the first one is the preferred value, but iD will still
760+
// recognise the alternative/s.
761+
"meter": ["m"],
762+
"centimeter": ["cm"],
763+
"millimeter": [""], // mm is the default unit in OSM, so the tag value should have no suffix.
764+
"yard": ["yd"]
765+
}
766+
}
767+
}
768+
```
769+
770+
Translations for the [`narrow` and `long` form](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) of each unit are bundled into iD-tagging-schema's locale files.
771+
743772
### Deprecations
744773

745774
Use `deprecated.json` ([Example](https://github.com/openstreetmap/id-tagging-schema/blob/main/data/deprecated.json), [Schema](https://github.com/ideditor/schema-builder/blob/main/schemas/deprecated.json)) to specify tag deprecations.

ā€Žeslint.config.mjsā€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default [
3333
'no-await-in-loop': 'error',
3434
'no-caller': 'error',
3535
'no-catch-shadow': 'error',
36-
'no-console': 'warn',
36+
'no-console': 'off',
3737
'no-constructor-return': 'error',
3838
'no-div-regex': 'error',
3939
'no-duplicate-imports': 'warn',

ā€Žlib/build.jsā€Ž

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,25 @@ const defaultsSchema = require('../schemas/preset_defaults.json');
2020
const deprecatedSchema = require('../schemas/deprecated.json');
2121
const discardedSchema = require('../schemas/discarded.json');
2222

23+
/** @import { TranslationOptions } from "./translations.js" */
24+
25+
/** @typedef {{
26+
inDirectory: string;
27+
interimDirectory: string;
28+
outDirectory: string;
29+
sourceLocale: string;
30+
taginfoProjectInfo: unknown,
31+
processCategories: null | unknown;
32+
processFields: null | unknown;
33+
processPresets: null | unknown;
34+
listReusedIcons: boolean;
35+
}} BuildOptions */
36+
37+
/** @typedef {Partial<BuildOptions & TranslationOptions>} Options */
38+
2339
let _currBuild = null;
2440

41+
/** @param {Options} options */
2542
function validateData(options) {
2643
const START = 'šŸ”¬ ' + styleText('yellow', 'Validating schema...');
2744
const END = 'šŸ‘ ' + styleText('green', 'schema okay');
@@ -36,6 +53,7 @@ function validateData(options) {
3653
process.stdout.write('\n');
3754
}
3855

56+
/** @param {Options} options */
3957
function buildDev(options) {
4058

4159
if (_currBuild) return _currBuild;
@@ -53,6 +71,7 @@ function buildDev(options) {
5371
process.stdout.write('\n');
5472
}
5573

74+
/** @param {Options} options */
5675
function buildDist(options) {
5776

5877
if (_currBuild) return _currBuild;
@@ -78,7 +97,8 @@ function buildDist(options) {
7897
});
7998
}
8099

81-
function processData(options, type) {
100+
/** @internal @param {Options} options @returns {Options} */
101+
export function getDefaultOptions(options) {
82102
if (!options) options = {};
83103
options = Object.assign({
84104
inDirectory: 'data',
@@ -91,7 +111,15 @@ function processData(options, type) {
91111
processPresets: null,
92112
listReusedIcons: false
93113
}, options);
114+
return options;
115+
}
94116

117+
/**
118+
* @param {Options} options
119+
* @param {'build-interim' | 'build-dist' | 'validate'} type
120+
*/
121+
function processData(options, type) {
122+
options = getDefaultOptions(options);
95123
const dataDir = './' + options.inDirectory;
96124

97125
// Translation strings
@@ -241,8 +269,8 @@ function generateCategories(dataDir, tstrings) {
241269
return categories;
242270
}
243271

244-
245-
function generateFields(dataDir, tstrings, searchableFieldIDs) {
272+
/** @internal */
273+
export function generateFields(dataDir, tstrings, searchableFieldIDs) {
246274
let fields = {};
247275

248276
fs.globSync(dataDir + '/fields/**/*.json', {

ā€Žlib/translations.jsā€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@ import fs from 'fs';
33
import fetch from 'node-fetch';
44
import YAML from 'js-yaml';
55
import { transifexApi } from '@transifex/api';
6+
import { getExternalTranslations } from './units.js';
67

78

9+
/** @typedef {{
10+
translOrgId: string;
11+
translProjectId: string;
12+
translResourceIds: string[];
13+
translReviewedOnly: false | string[];
14+
inDirectory: string;
15+
outDirectory: string;
16+
sourceLocale: string;
17+
}} TranslationOptions */
18+
19+
/** @param {Partial<TranslationOptions>} options */
820
function fetchTranslations(options) {
921

1022
// Transifex doesn't allow anonymous downloading
@@ -202,6 +214,8 @@ function fetchTranslations(options) {
202214
for (let code in allStrings) {
203215
let obj = {};
204216
obj[code] = allStrings[code] || {};
217+
Object.assign(obj[code], getExternalTranslations(code, options));
218+
205219
fs.writeFileSync(`${outDir}/${code}.json`, JSON.stringify(obj, null, 4));
206220
fs.writeFileSync(`${outDir}/${code}.min.json`, JSON.stringify(obj));
207221
}

ā€Žlib/units.jsā€Ž

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// @ts-check
2+
import { createRequire } from 'node:module';
3+
import { generateFields, getDefaultOptions } from './build.js';
4+
5+
const require = createRequire(import.meta.url);
6+
7+
let cachedFields;
8+
9+
/**
10+
* @param {string} locale
11+
* @param {Partial<import('./translations.js').TranslationOptions>} options
12+
*/
13+
export function getExternalTranslations(locale, options) {
14+
options = getDefaultOptions(options);
15+
const language = locale.split('-')[0];
16+
17+
cachedFields ||= generateFields(options.inDirectory, { fields: {} }, {});
18+
19+
let localeData;
20+
let languageData;
21+
try {
22+
localeData = require(`cldr-units-full/main/${locale}/units.json`);
23+
} catch {
24+
// ignore
25+
}
26+
try {
27+
languageData = require(`cldr-units-full/main/${language}/units.json`);
28+
} catch {
29+
// ignore
30+
}
31+
32+
if (!localeData && !languageData) {
33+
console.warn(`No CLDR data for ${language}`);
34+
}
35+
36+
const output = {};
37+
38+
for (const field of Object.values(cachedFields)) {
39+
if (!field.measurement) continue;
40+
41+
const { dimension, units } = field.measurement;
42+
43+
for (const unit in units) {
44+
for (const type of ['long', 'narrow']) {
45+
const translation =
46+
localeData?.main[locale].units[type][`${dimension}-${unit}`]
47+
.displayName ||
48+
languageData?.main[language].units[type][`${dimension}-${unit}`]
49+
.displayName;
50+
51+
output[dimension] ||= {};
52+
output[dimension][unit] ||= {};
53+
output[dimension][unit][type] = translation;
54+
}
55+
}
56+
}
57+
58+
return { units: output };
59+
}

ā€Žpackage-lock.jsonā€Ž

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ā€Žpackage.jsonā€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"exports": "./lib/index.js",
1414
"dependencies": {
1515
"@transifex/api": "^7.1.0",
16+
"cldr-core": "^47.0.0",
17+
"cldr-units-full": "^47.0.0",
1618
"js-yaml": "^4.0.0",
1719
"json-schema-to-typescript-lite": "^15.0.0",
1820
"jsonschema": "^1.1.0",
@@ -31,6 +33,7 @@
3133
"node": ">=22"
3234
},
3335
"scripts": {
36+
"build": "node scripts/build-schema.js",
3437
"lint": "eslint lib",
3538
"lint:fix": "eslint lib --fix",
3639
"test": "NODE_OPTIONS=--experimental-vm-modules jest schema-builder.test.js"

0 commit comments

Comments
Ā (0)
⚔