diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index 1c579306..9a214277 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -17,12 +17,12 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-node@v3 with: - node-version: 18.x + node-version-file: 'app/.nvmrc' - run: echo "VITE_GIT_SHA=$(git rev-parse --short HEAD)" >> app/.env - name: Check styles - run: npm install && npx tsc --noEmit && npm run check + run: npm ci && npm run tsc && npm run check working-directory: styles - - run: npm install && npx tsc --noEmit && npm run check && npx vite build + - run: npm ci && npm run tsc && npm run check && npm run build working-directory: app - run: python .github/check_examples.py - uses: peaceiris/actions-gh-pages@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 479548d9..9b19e3b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Styles v4.0.0-alpha.0 +------ +- Add lang and script parameters to TypeScript style generation [#275] + Tiles v4.0.0-alpha.3 ------ - Replace Natural Earth places at low zooms with OSM [#289] diff --git a/README.md b/README.md index 05c6b642..d440b7a3 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ java -jar target/*-with-deps.jar --download --force --area=monaco ```shell cd app -npm install +nvm use +npm ci npm run dev ``` diff --git a/app/.nvmrc b/app/.nvmrc new file mode 120000 index 00000000..7a98bec4 --- /dev/null +++ b/app/.nvmrc @@ -0,0 +1 @@ +../styles/.nvmrc \ No newline at end of file diff --git a/app/package.json b/app/package.json index cc39c14d..7a3052c6 100644 --- a/app/package.json +++ b/app/package.json @@ -5,10 +5,12 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", + "tsc": "tsc --noEmit", "preview": "vite preview", "test": "vitest", - "check": "biome check src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space" + "check": "biome check src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space", + "format": "biome format --write src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space" }, "dependencies": { "maplibre-gl": "^4.1.3", diff --git a/app/src/MapViewComponent.tsx b/app/src/MapViewComponent.tsx index bd659159..9a7da262 100644 --- a/app/src/MapViewComponent.tsx +++ b/app/src/MapViewComponent.tsx @@ -13,6 +13,7 @@ import { import { renderToString } from "react-dom/server"; import { useDropzone } from "react-dropzone"; import layers from "../../styles/src/index.ts"; +import { language_script_pairs } from "../../styles/src/language.ts"; import { LayerSpecification } from "@maplibre/maplibre-gl-style-spec"; @@ -68,6 +69,7 @@ export const isValidPMTiles = (tiles?: string): boolean => { function getMaplibreStyle( theme: string, + lang: string, localSprites: boolean, tiles?: string, npmLayers?: LayerSpecification[], @@ -121,14 +123,19 @@ function getMaplibreStyle( if (npmLayers && npmLayers.length > 0) { style.layers = style.layers.concat(npmLayers); } else { - style.layers = style.layers.concat(layers("protomaps", theme)); + style.layers = style.layers.concat(layers("protomaps", theme, lang)); } return style; } -function StyleJsonPane(props: { theme: string }) { +function StyleJsonPane(props: { theme: string; lang: string }) { const stringified = JSON.stringify( - getMaplibreStyle(props.theme, false, "https://example.com/tiles.json"), + getMaplibreStyle( + props.theme, + props.lang, + false, + "https://example.com/tiles.json", + ), null, 4, ); @@ -150,6 +157,7 @@ function StyleJsonPane(props: { theme: string }) { function MapLibreView(props: { theme: string; + lang: string; localSprites: boolean; tiles?: string; npmLayers: LayerSpecification[]; @@ -173,7 +181,7 @@ function MapLibreView(props: { const map = new maplibregl.Map({ hash: "map", container: "map", - style: getMaplibreStyle("", false), + style: getMaplibreStyle("", "en", false), }); map.addControl(new maplibregl.NavigationControl()); @@ -196,7 +204,7 @@ function MapLibreView(props: { maxWidth: "none", }); - map.on("mousedown", (e) => { + map.on("contextmenu", (e) => { const features = map.queryRenderedFeatures(e.point); if (features.length) { const content = renderToString( @@ -251,6 +259,7 @@ function MapLibreView(props: { mapRef.current.setStyle( getMaplibreStyle( props.theme, + props.lang, props.localSprites, props.tiles, props.npmLayers, @@ -264,6 +273,7 @@ function MapLibreView(props: { }, [ props.tiles, props.theme, + props.lang, props.localSprites, props.npmLayers, props.droppedArchive, @@ -276,6 +286,7 @@ function MapLibreView(props: { export default function MapViewComponent() { const hash = parseHash(location.hash); const [theme, setTheme] = useState(hash.theme || "light"); + const [lang, setLang] = useState(hash.lang || "en"); const [tiles, setTiles] = useState(hash.tiles); const [localSprites, setLocalSprites] = useState( hash.local_sprites === "true", @@ -291,6 +302,7 @@ export default function MapViewComponent() { useEffect(() => { const record = { theme: theme, + lang: lang, tiles: tiles, local_sprites: localSprites ? "true" : undefined, npm_version: publishedStyleVersion, @@ -368,6 +380,8 @@ export default function MapViewComponent() { })(); }, [publishedStyleVersion, theme]); + language_script_pairs.sort((a, b) => a.full_name.localeCompare(b.full_name)); + return (
); diff --git a/app/src/VisualTestsComponent.tsx b/app/src/VisualTestsComponent.tsx index af804143..3200727b 100644 --- a/app/src/VisualTestsComponent.tsx +++ b/app/src/VisualTestsComponent.tsx @@ -240,7 +240,7 @@ export default function VisualTestsComponent() { const leftLayers = await layersForVersion(leftLayersStr); const rightLayers = rightLayersStr ? await layersForVersion(rightLayersStr) - : layers("protomaps", "light"); + : layers("protomaps", "light", "en", "Latin"); setDisplayInfo([ leftTiles, diff --git a/styles/README.md b/styles/README.md index 8b89b3ca..ff95f342 100644 --- a/styles/README.md +++ b/styles/README.md @@ -7,12 +7,28 @@ nvm use npm ci ``` -## Generate JSON layers +## Generate JSON styles in all themes and languages -This only generates the `layers` key of a style; you'll assemble `source`, `glyphs`, `sprite` yourself. +To generate style.json files in all themes and supported languages, run: -`generate-layers SOURCE_NAME THEME` +``` +npm run generate-styles https://example.com/your-tilejson-url.json +``` + +Note that you have to replace the tilejson url with your own. + +This will create files in the `dist/styles/` folder like this: ``` -npm run generate-layers protomaps light +dist/ + styles/ + black/ + ar.json + bg.json + ... + contrast/ + ar.json + bg.json + ... + ... ``` \ No newline at end of file diff --git a/styles/package.json b/styles/package.json index 0bdebbaf..3589dadf 100644 --- a/styles/package.json +++ b/styles/package.json @@ -1,6 +1,6 @@ { "name": "protomaps-themes-base", - "version": "3.1.0", + "version": "4.0.0-alpha.0", "description": "Protomaps basemap themes for MapLibre GL JS", "type": "module", "main": "dist/cjs/index.cjs", @@ -24,17 +24,13 @@ "src" ], "scripts": { - "generate-layers": "tsx src/generate_layers.ts", - "dist-light": "mkdir -p dist/layers && npm run --silent generate-layers protomaps light > dist/layers/light.json", - "dist-dark": "mkdir -p dist/layers && npm run --silent generate-layers protomaps dark > dist/layers/dark.json", - "dist-white": "mkdir -p dist/layers && npm run --silent generate-layers protomaps white > dist/layers/white.json", - "dist-grayscale": "mkdir -p dist/layers && npm run --silent generate-layers protomaps grayscale > dist/layers/grayscale.json", - "dist-black": "mkdir -p dist/layers && npm run --silent generate-layers protomaps black > dist/layers/black.json", - "dist-all": "npm run dist-light && npm run dist-dark && npm run dist-white && npm run dist-grayscale && npm run dist-black", - "build": "tsup && npm run dist-all", + "generate-styles": "tsx src/generate_styles.ts", + "build": "tsup && npm run generate-styles https://example.com/tiles.json", "test": "tsx test/index.test.ts", - "tsc": "tsc --noEmit --watch", - "check": "biome check src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space" + "tsc-watch": "tsc --noEmit --watch", + "tsc": "tsc --noEmit", + "check": "biome check src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space", + "format": "biome format --write src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space" }, "repository": { "type": "git", diff --git a/styles/src/base_layers.ts b/styles/src/base_layers.ts index 743e225c..7b889f00 100644 --- a/styles/src/base_layers.ts +++ b/styles/src/base_layers.ts @@ -1,4 +1,9 @@ -import { LayerSpecification } from "@maplibre/maplibre-gl-style-spec"; +import { + DataDrivenPropertyValueSpecification, + ExpressionSpecification, + LayerSpecification, +} from "@maplibre/maplibre-gl-style-spec"; +import { get_country_name, get_multiline_name } from "./language"; import { Theme } from "./themes"; export function nolabels_layers( @@ -1634,7 +1639,12 @@ export function nolabels_layers( ]; } -export function labels_layers(source: string, t: Theme): LayerSpecification[] { +export function labels_layers( + source: string, + t: Theme, + lang: string, + script?: string, +): LayerSpecification[] { return [ { id: "physical_line_waterway_label", @@ -1646,7 +1656,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { layout: { "symbol-placement": "line", "text-font": ["Noto Sans Regular"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": 12, "text-letter-spacing": 0.3, }, @@ -1662,7 +1675,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { filter: ["any", ["==", "pmap:kind", "peak"]], layout: { "text-font": ["Noto Sans Italic"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": ["interpolate", ["linear"], ["zoom"], 10, 8, 16, 12], "text-letter-spacing": 0.1, "text-max-width": 9, @@ -1683,7 +1699,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { "symbol-sort-key": ["get", "pmap:min_zoom"], "symbol-placement": "line", "text-font": ["Noto Sans Regular"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": 12, }, paint: { @@ -1713,7 +1732,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { ], layout: { "text-font": ["Noto Sans Medium"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 10, 12], "text-letter-spacing": 0.1, "text-max-width": 9, @@ -1731,7 +1753,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { filter: ["any", ["in", "pmap:kind", "lake", "water"]], layout: { "text-font": ["Noto Sans Medium"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": ["interpolate", ["linear"], ["zoom"], 3, 0, 6, 12, 10, 12], "text-letter-spacing": 0.1, "text-max-width": 9, @@ -1754,7 +1779,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { "symbol-sort-key": ["get", "pmap:min_zoom"], "symbol-placement": "line", "text-font": ["Noto Sans Regular"], - "text-field": ["get", "name"], + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-size": 12, }, paint: { @@ -1771,7 +1799,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { filter: ["==", "pmap:kind", "neighbourhood"], layout: { "symbol-sort-key": ["get", "pmap:min_zoom"], - "text-field": "{name}", + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-font": ["Noto Sans Regular"], "text-max-width": 7, "text-letter-spacing": 0.1, @@ -1816,7 +1847,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { layout: { "icon-image": ["step", ["zoom"], "townspot", 8, ""], "icon-size": 0.7, - "text-field": "{name}", + "text-field": get_multiline_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-font": [ "case", ["<=", ["get", "pmap:min_zoom"], 5], @@ -1930,7 +1964,7 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { ["zoom"], ["get", "name:short"], 6, - ["get", "name"], + get_multiline_name(lang, script) as ExpressionSpecification, ], "text-font": ["Noto Sans Regular"], "text-size": ["interpolate", ["linear"], ["zoom"], 3, 11, 7, 16], @@ -1952,7 +1986,10 @@ export function labels_layers(source: string, t: Theme): LayerSpecification[] { filter: ["==", "pmap:kind", "country"], layout: { "symbol-sort-key": ["get", "pmap:min_zoom"], - "text-field": "{name}", + "text-field": get_country_name( + lang, + script, + ) as DataDrivenPropertyValueSpecification, "text-font": ["Noto Sans Medium"], "text-size": [ "interpolate", diff --git a/styles/src/generate_layers.ts b/styles/src/generate_layers.ts deleted file mode 100644 index 78ef8cc1..00000000 --- a/styles/src/generate_layers.ts +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-nocheck -declare const process: unknown; - -import i from "./index"; - -if (process.argv.length < 2) { - process.stdout.write("usage: generate-layers SOURCE_NAME THEME"); - process.exit(1); -} -const args = process.argv.slice(2); - -process.stdout.write(JSON.stringify(i(args[0], args[1]), null, 2)); diff --git a/styles/src/generate_styles.ts b/styles/src/generate_styles.ts new file mode 100644 index 00000000..7564f38d --- /dev/null +++ b/styles/src/generate_styles.ts @@ -0,0 +1,53 @@ +// @ts-nocheck +declare const process: unknown; + +import fs from "fs"; +import { writeFile } from "fs/promises"; +import i from "./index"; +import { language_script_pairs } from "./language"; +import themes from "./themes"; + +if (process.argv.length < 2) { + process.stdout.write("usage: generate-styles TILEJSON_URL"); + process.exit(1); +} + +const args = process.argv.slice(2); +const tileJson = args[0]; + +for (const theme of Object.keys(themes)) { + for (const { lang, full_name, script } of language_script_pairs) { + const layers = i("protomaps", theme, lang, script); + + const style = { + version: 8, + sources: { + protomaps: { + type: "vector", + attribution: + 'Protomaps © OpenStreetMap', + url: tileJson, + }, + }, + layers: layers, + sprite: "https://protomaps.github.io/basemaps-assets/sprites/v3/light", + glyphs: + "https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf", + }; + + const directory = `dist/styles/${theme}`; + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + console.log(`Directory ${directory} created successfully!`); + } + + try { + await writeFile( + `${directory}/${lang}.json`, + JSON.stringify(style, null, 2), + ); + } catch (err) { + console.error("An error occurred while writing to the file:", err); + } + } +} diff --git a/styles/src/index.ts b/styles/src/index.ts index 1dbd2669..1a2f4c65 100644 --- a/styles/src/index.ts +++ b/styles/src/index.ts @@ -2,9 +2,16 @@ import { LayerSpecification } from "@maplibre/maplibre-gl-style-spec"; import { labels_layers, nolabels_layers } from "./base_layers"; import themes, { Theme } from "./themes"; -export default function (source: string, key: string): LayerSpecification[] { +export default function ( + source: string, + key: string, + lang: string, + script?: string, +): LayerSpecification[] { const theme = themes[key]; - return nolabels_layers(source, theme).concat(labels_layers(source, theme)); + return nolabels_layers(source, theme).concat( + labels_layers(source, theme, lang, script), + ); } export function noLabels(source: string, key: string): LayerSpecification[] { @@ -12,26 +19,37 @@ export function noLabels(source: string, key: string): LayerSpecification[] { return nolabels_layers(source, theme); } -export function labels(source: string, key: string): LayerSpecification[] { +export function labels( + source: string, + key: string, + lang: string, + script?: string, +): LayerSpecification[] { const theme = themes[key]; - return labels_layers(source, theme); + return labels_layers(source, theme, lang, script); } export function layersWithCustomTheme( source: string, theme: Theme, + lang: string, + script?: string, ): LayerSpecification[] { - return nolabels_layers(source, theme).concat(labels_layers(source, theme)); + return nolabels_layers(source, theme).concat( + labels_layers(source, theme, lang, script), + ); } export function layersWithPartialCustomTheme( source: string, key: string, partialTheme: Partial, + lang: string, + script?: string, ): LayerSpecification[] { const mergedTheme = { ...themes[key], ...partialTheme }; return nolabels_layers(source, mergedTheme).concat( - labels_layers(source, mergedTheme), + labels_layers(source, mergedTheme, lang, script), ); } @@ -45,6 +63,8 @@ export function noLabelsWithCustomTheme( export function labelsWithCustomTheme( source: string, theme: Theme, + lang: string, + script?: string, ): LayerSpecification[] { - return labels_layers(source, theme); + return labels_layers(source, theme, lang, script); } diff --git a/styles/src/language.ts b/styles/src/language.ts new file mode 100644 index 00000000..a11a5209 --- /dev/null +++ b/styles/src/language.ts @@ -0,0 +1,507 @@ +function get_name_block(script_segment: "name" | "name2" | "name3") { + let script = "script"; + + if (script_segment === "name") { + script = "script"; + } else if (script_segment === "name2") { + script = "script2"; + } else if (script_segment === "name3") { + script = "script3"; + } + + return [ + [ + "coalesce", + ["get", `pmap:pgf:${script_segment}`], + ["get", script_segment], + ], + { + "text-font": [ + "case", + ["==", ["get", `pmap:${script}`], "Devanagari"], + ["literal", ["Noto Sans Devanagari Regular v1"]], + ["literal", ["Noto Sans Regular"]], + ], + }, + ]; +} + +function is_not_in_target_script( + lang: string, + script: string, + script_segment: "name" | "name2" | "name3", +) { + let suffix = "name"; + if (script_segment === "name") { + suffix = ""; + } else if (script_segment === "name2") { + suffix = "2"; + } else if (script_segment === "name3") { + suffix = "3"; + } + + if (script === "Latin") { + return ["has", `pmap:script${suffix}`]; + } + + if (lang === "ja") { + return [ + "all", + ["!=", ["get", `pmap:script${suffix}`], "Han"], + ["!=", ["get", `pmap:script${suffix}`], "Hiragana"], + ["!=", ["get", `pmap:script${suffix}`], "Katakana"], + ["!=", ["get", `pmap:script${suffix}`], "Mixed-Japanese"], + ]; + } + + return ["!=", ["get", `pmap:script${suffix}`], script]; +} + +function get_font_formatting(script: string) { + if (script === "Devanagari") { + return { + "text-font": ["literal", ["Noto Sans Devanagari Regular v1"]], + }; + } + return {}; +} + +function get_default_script(lang: string) { + const pair = language_script_pairs.find((d) => d.lang === lang); + return pair === undefined ? "Latin" : pair.script; +} + +export function get_country_name(lang: string, script?: string) { + const _script = script || get_default_script(lang); + let name_prefix: string; + if (_script === "Devanagari") { + name_prefix = "pmap:pgf:"; + } else { + name_prefix = ""; + } + return [ + "format", + ["coalesce", ["get", `${name_prefix}name:${lang}`], ["get", "name:en"]], + get_font_formatting(_script), + ]; +} + +export function get_multiline_name(lang: string, script?: string) { + const _script = script || get_default_script(lang); + let name_prefix: string; + if (_script === "Devanagari") { + name_prefix = "pmap:pgf:"; + } else { + name_prefix = ""; + } + + const result = [ + "case", + [ + "all", + ["any", ["has", "name"], ["has", "pmap:pgf:name"]], + ["!", ["any", ["has", "name2"], ["has", "pmap:pgf:name2"]]], + ["!", ["any", ["has", "name3"], ["has", "pmap:pgf:name3"]]], + ], + // The local name has 1 script segment: `name` + [ + "case", + is_not_in_target_script(lang, _script, "name"), + // `name` is not in the target script + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "name:en"], // Always fallback to English + ], + get_font_formatting(_script), + "\n", + {}, + [ + "case", + [ + "all", + ["!", ["has", `${name_prefix}name:${lang}`]], + ["has", "name:en"], + ["!", ["has", "pmap:script"]], + ], + // We did fallback to English in the first line and `name` is Latin + "", + ["coalesce", ["get", "pmap:pgf:name"], ["get", "name"]], + ], + { + "text-font": [ + "case", + ["==", ["get", "pmap:script"], "Devanagari"], + ["literal", ["Noto Sans Devanagari Regular v1"]], + ["literal", ["Noto Sans Regular"]], + ], + }, + ], + // `name` is in the target script + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name"], + ["get", "name"], + ], + get_font_formatting(_script), + ], + ], + [ + "all", + ["any", ["has", "name"], ["has", "pmap:pgf:name"]], + ["any", ["has", "name2"], ["has", "pmap:pgf:name2"]], + ["!", ["any", ["has", "name3"], ["has", "pmap:pgf:name3"]]], + ], + // The local name has 2 script segments: `name` and `name2` + [ + "case", + [ + "all", + is_not_in_target_script(lang, _script, "name"), + is_not_in_target_script(lang, _script, "name2"), + ], + // Both `name` and `name2` are not in the target script + [ + "format", + ["get", `${name_prefix}name:${lang}`], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name"), + "\n", + {}, + ...get_name_block("name2"), + ], + // Either `name` or `name2` is in the target script + [ + "case", + is_not_in_target_script(lang, _script, "name2"), + // `name2` is not in the target script, therefore `name` is in the target script + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name"], + ["get", "name"], + ], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name2"), + ], + // `name2` is in the target script, therefore `name` is not in the target script + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name2"], + ["get", "name2"], + ], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name"), + ], + ], + ], + // The local name has 3 script segments: `name`, `name2`, and `name3` + [ + "case", + [ + "all", + is_not_in_target_script(lang, _script, "name"), + is_not_in_target_script(lang, _script, "name2"), + is_not_in_target_script(lang, _script, "name3"), + ], + // All three `name`, `name2`, and `name3` are not in the target script + [ + "format", + ["get", `${name_prefix}name:${lang}`], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name"), + "\n", + {}, + ...get_name_block("name2"), + "\n", + {}, + ...get_name_block("name3"), + ], + // Exactly one of the 3 script segments `name`, `name2`, or `name3` is in the target script + [ + "case", + ["!", is_not_in_target_script(lang, _script, "name")], + // `name` is in the target script, and `name2` and `name3` are not + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name"], + ["get", "name"], + ], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name2"), + "\n", + {}, + ...get_name_block("name3"), + ], + ["!", is_not_in_target_script(lang, _script, "name2")], + // `name2` is in the target script, and `name` and `name3` are not + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name2"], + ["get", "name2"], + ], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name"), + "\n", + {}, + ...get_name_block("name3"), + ], + // `name3` is in the target script, and `name` and `name2` are not + [ + "format", + [ + "coalesce", + ["get", `${name_prefix}name:${lang}`], + ["get", "pmap:pgf:name3"], + ["get", "name3"], + ], + get_font_formatting(_script), + "\n", + {}, + ...get_name_block("name"), + "\n", + {}, + ...get_name_block("name2"), + ], + ], + ], + ]; + return result; +} + +export const language_script_pairs = [ + { + lang: "ar", + full_name: "Arabic", + script: "Arabic", + }, + { + lang: "cs", + full_name: "Czech", + script: "Latin", + }, + { + lang: "bg", + full_name: "Bulgarian", + script: "Cyrillic", + }, + { + lang: "da", + full_name: "Danish", + script: "Latin", + }, + { + lang: "de", + full_name: "German", + script: "Latin", + }, + { + lang: "el", + full_name: "Greek", + script: "Greek", + }, + { + lang: "en", + full_name: "English", + script: "Latin", + }, + { + lang: "es", + full_name: "Spanish", + script: "Latin", + }, + { + lang: "et", + full_name: "Estonian", + script: "Latin", + }, + { + lang: "fa", + full_name: "Persian", + script: "Arabic", + }, + { + lang: "fi", + full_name: "Finnish", + script: "Latin", + }, + { + lang: "fr", + full_name: "French", + script: "Latin", + }, + { + lang: "ga", + full_name: "Irish", + script: "Latin", + }, + { + lang: "he", + full_name: "Hebrew", + script: "Hebrew", + }, + { + lang: "hi", + full_name: "Hindi", + script: "Devanagari", + }, + { + lang: "hr", + full_name: "Croatian", + script: "Latin", + }, + { + lang: "hu", + full_name: "Hungarian", + script: "Latin", + }, + { + lang: "id", + full_name: "Indonesian", + script: "Latin", + }, + { + lang: "it", + full_name: "Italian", + script: "Latin", + }, + { + lang: "ja", + full_name: "Japanese", + // Japanese is a special case, using multiple scripts + script: "", + }, + { + lang: "ko", + full_name: "Korean", + script: "Hangul", + }, + { + lang: "lt", + full_name: "Lithuanian", + script: "Latin", + }, + { + lang: "lv", + full_name: "Latvian", + script: "Latin", + }, + { + lang: "ne", + full_name: "Nepali", + script: "Devanagari", + }, + { + lang: "nl", + full_name: "Dutch", + script: "Latin", + }, + { + lang: "no", + full_name: "Norwegian", + script: "Latin", + }, + { + lang: "mr", + full_name: "Marathi", + script: "Devanagari", + }, + { + lang: "mt", + full_name: "Maltese", + script: "Latin", + }, + { + lang: "pl", + full_name: "Polish", + script: "Latin", + }, + { + lang: "pt", + full_name: "Portuguese", + script: "Latin", + }, + { + lang: "ro", + full_name: "Romanian", + script: "Latin", + }, + { + lang: "ru", + full_name: "Russian", + script: "Cyrillic", + }, + { + lang: "sk", + full_name: "Slovak", + script: "Latin", + }, + { + lang: "sl", + full_name: "Slovenian", + script: "Latin", + }, + { + lang: "sv", + full_name: "Swedish", + script: "Latin", + }, + { + lang: "tr", + full_name: "Turkish", + script: "Latin", + }, + { + lang: "uk", + full_name: "Ukrainian", + script: "Cyrillic", + }, + { + lang: "ur", + full_name: "Urdu", + script: "Arabic", + }, + { + lang: "vi", + full_name: "Vietnamese", + script: "Latin", + }, + { + lang: "zh-Hans", + full_name: "Chinese (Simplified)", + script: "Han", + }, + { + lang: "zh-Hant", + full_name: "Chinese (Traditional)", + script: "Han", + }, +];