From 1baad9f85a48857532b192ebe8c9f817148138f8 Mon Sep 17 00:00:00 2001
From: Chris Wilkinson <c.wilkinson@elifesciences.org>
Date: Thu, 20 Jun 2024 12:24:32 +0100
Subject: [PATCH] Group the requests by preprint language

---
 package-lock.json         | 11 ++++++++++-
 package.json              |  3 ++-
 src/data/requests.json.ts |  8 +++++++-
 src/lib/LanguageCode.ts   | 10 ++++++++++
 src/requests.md           | 16 ++++++++++++++--
 5 files changed, 43 insertions(+), 5 deletions(-)
 create mode 100644 src/lib/LanguageCode.ts

diff --git a/package-lock.json b/package-lock.json
index 139901f..7f5c7e8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,8 @@
         "@observablehq/framework": "^1.9.0",
         "d3-dsv": "^3.0.1",
         "d3-time-format": "^4.1.0",
-        "effect": "^3.3.5"
+        "effect": "^3.3.5",
+        "iso-639-1": "^3.1.2"
       },
       "devDependencies": {
         "@trivago/prettier-plugin-sort-imports": "^4.3.0",
@@ -2562,6 +2563,14 @@
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
     },
+    "node_modules/iso-639-1": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.2.tgz",
+      "integrity": "sha512-Le7BRl3Jt9URvaiEHJCDEdvPZCfhiQoXnFgLAWNRhzFMwRFdWO7/5tLRQbiPzE394I9xd7KdRCM7S6qdOhwG5A==",
+      "engines": {
+        "node": ">=6.0"
+      }
+    },
     "node_modules/isoformat": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz",
diff --git a/package.json b/package.json
index 41f56a2..b8bf856 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,8 @@
     "@observablehq/framework": "^1.9.0",
     "d3-dsv": "^3.0.1",
     "d3-time-format": "^4.1.0",
-    "effect": "^3.3.5"
+    "effect": "^3.3.5",
+    "iso-639-1": "^3.1.2"
   },
   "devDependencies": {
     "@trivago/prettier-plugin-sort-imports": "^4.3.0",
diff --git a/src/data/requests.json.ts b/src/data/requests.json.ts
index 3685d8e..3188166 100644
--- a/src/data/requests.json.ts
+++ b/src/data/requests.json.ts
@@ -2,9 +2,15 @@ import { HttpClient, Terminal } from '@effect/platform'
 import { NodeTerminal } from '@effect/platform-node'
 import { Schema } from '@effect/schema'
 import { Effect } from 'effect'
+import * as LanguageCode from '../lib/LanguageCode.js'
 import * as Temporal from '../lib/Temporal.js'
 
-const Requests = Schema.Array(Schema.Struct({ timestamp: Temporal.InstantFromStringSchema }))
+const Requests = Schema.Array(
+  Schema.Struct({
+    timestamp: Temporal.InstantFromStringSchema,
+    language: Schema.optional(LanguageCode.LanguageCodeSchema, { nullable: true }),
+  }),
+)
 
 const program = Effect.gen(function* () {
   const terminal = yield* Terminal.Terminal
diff --git a/src/lib/LanguageCode.ts b/src/lib/LanguageCode.ts
new file mode 100644
index 0000000..ac06991
--- /dev/null
+++ b/src/lib/LanguageCode.ts
@@ -0,0 +1,10 @@
+import { Schema } from '@effect/schema'
+import type { Predicate } from 'effect'
+import iso6391, { type LanguageCode } from 'iso-639-1'
+
+export { type LanguageCode } from 'iso-639-1'
+
+export const isLanguageCode: Predicate.Refinement<unknown, LanguageCode> = (u): u is LanguageCode =>
+  typeof u === 'string' && iso6391.validate(u)
+
+export const LanguageCodeSchema: Schema.Schema<LanguageCode, string> = Schema.String.pipe(Schema.filter(isLanguageCode))
diff --git a/src/requests.md b/src/requests.md
index cef0662..76d854e 100644
--- a/src/requests.md
+++ b/src/requests.md
@@ -8,6 +8,7 @@ toc: false
 
 ```js
 const parseTimestamp = d3.utcParse('%Y-%m-%dT%H:%M:%S.%LZ')
+const languageNames = new Intl.DisplayNames(['en-US'], { type: 'language' })
 
 const requests = FileAttachment('./data/requests.json')
   .json()
@@ -27,13 +28,24 @@ function requestsTimeline({ width } = {}) {
     title: 'Requests per week',
     width: Math.max(width, 600),
     height: 400,
-    color: {},
+    color: {
+      legend: true,
+      tickFormat: d => (d ? languageNames.of(d) : 'Not yet detected'),
+    },
     y: { grid: true, label: 'Requests' },
     x: { label: '' },
     marks: [
       Plot.rectY(
         requests,
-        Plot.binX({ y: 'count' }, { x: 'timestamp', interval: d3.utcWeek, fill: 'var(--theme-foreground-focus)' }),
+        Plot.binX(
+          { y: 'count' },
+          {
+            x: 'timestamp',
+            interval: d3.utcWeek,
+            fill: 'language',
+            order: ['en', 'es', 'pt'],
+          },
+        ),
       ),
       Plot.ruleY([0]),
     ],