From 3d4608024cc2b46539c7f788e0e524aec0a9afa3 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sat, 17 Aug 2024 14:40:04 +0200 Subject: [PATCH] allow free exam labels --- scripts/merge-youtube-data.mjs | 2 +- .../TechniqueChooser.test.tsx | 106 +++++++++++++++--- .../TechniqueChooser/TechniqueChooser.tsx | 28 ++++- src/core/model/Dojo.test-helper.ts | 21 +++- src/core/model/Exam.test-helper.ts | 5 +- src/core/model/Exam.ts | 14 ++- .../resolveExamTables.test.ts | 10 +- .../aikido-dojo-darmstadt/exams/additional.ts | 7 +- .../dojos/aikido-dojo-darmstadt/exams/dan1.ts | 5 +- .../dojos/aikido-dojo-darmstadt/exams/kyu1.ts | 5 +- .../dojos/aikido-dojo-darmstadt/exams/kyu2.ts | 5 +- .../dojos/aikido-dojo-darmstadt/exams/kyu3.ts | 5 +- .../dojos/aikido-dojo-darmstadt/exams/kyu4.ts | 5 +- .../dojos/aikido-dojo-darmstadt/exams/kyu5.ts | 5 +- .../aikido-foederation/exams/additional.ts | 7 +- .../dojos/aikido-foederation/exams/dan1.ts | 5 +- .../dojos/aikido-foederation/exams/dan2.ts | 5 +- .../dojos/aikido-foederation/exams/dan3.ts | 5 +- .../dojos/aikido-foederation/exams/kyu1.ts | 6 +- .../dojos/aikido-foederation/exams/kyu2.ts | 6 +- .../dojos/aikido-foederation/exams/kyu3.ts | 5 +- .../dojos/aikido-foederation/exams/kyu4.ts | 6 +- .../dojos/aikido-foederation/exams/kyu5.ts | 5 +- src/i18n/common/de.json | 2 +- src/i18n/common/en.json | 2 +- 25 files changed, 232 insertions(+), 45 deletions(-) diff --git a/scripts/merge-youtube-data.mjs b/scripts/merge-youtube-data.mjs index c9a46cd..da58b10 100644 --- a/scripts/merge-youtube-data.mjs +++ b/scripts/merge-youtube-data.mjs @@ -10,7 +10,7 @@ for (const exam of Object.values(tables.exams)) { metadata.youtube = findVideo(technique); } } - result[exam.labelKey] = exam; + result[exam.id] = exam; } console.log(JSON.stringify(result, 0, 2)); diff --git a/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.test.tsx b/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.test.tsx index b551172..c13d8d4 100644 --- a/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.test.tsx +++ b/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.test.tsx @@ -15,9 +15,24 @@ describe("Chooser.test.tsx", async () => { const dojo = createResolvedDojo({ details: createDojoDetails({ exams: [ - createExam({ labelKey: "chooser.button.kyu5" }), - createExam({ labelKey: "chooser.button.kyu4" }), - createExam({ labelKey: "chooser.button.kyu3" }), + createExam({ + label: { + type: "wellknown", + key: "kyu5", + }, + }), + createExam({ + label: { + type: "wellknown", + key: "kyu4", + }, + }), + createExam({ + label: { + type: "wellknown", + key: "kyu3", + }, + }), ], }), }); @@ -30,7 +45,14 @@ describe("Chooser.test.tsx", async () => { it("clicking an exam selects it", async () => { const dojo = createResolvedDojo({ details: createDojoDetails({ - exams: [createExam({ labelKey: "chooser.button.kyu5" })], + exams: [ + createExam({ + label: { + type: "wellknown", + key: "kyu5", + }, + }), + ], }), }); render(() => ); @@ -44,7 +66,14 @@ describe("Chooser.test.tsx", async () => { it("clicking an selected exam deselects it", async () => { const dojo = createResolvedDojo({ details: createDojoDetails({ - exams: [createExam({ labelKey: "chooser.button.kyu5" })], + exams: [ + createExam({ + label: { + type: "wellknown", + key: "kyu5", + }, + }), + ], }), }); render(() => ); @@ -60,7 +89,10 @@ describe("Chooser.test.tsx", async () => { details: createDojoDetails({ exams: [ createExam({ - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "suwari waza": { "ai hanmi katate dori": { @@ -86,7 +118,10 @@ describe("Chooser.test.tsx", async () => { details: createDojoDetails({ exams: [ createExam({ - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "suwari waza": { "ai hanmi katate dori": { @@ -108,7 +143,10 @@ describe("Chooser.test.tsx", async () => { details: createDojoDetails({ exams: [ createExam({ - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "hanmi handachi waza": { "ai hanmi katate dori": { @@ -138,7 +176,10 @@ describe("Chooser.test.tsx", async () => { details: createDojoDetails({ exams: [ createExam({ - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "hanmi handachi waza": { "ai hanmi katate dori": { @@ -163,8 +204,20 @@ describe("Chooser.test.tsx", async () => { const dojo = createResolvedDojo({ details: createDojoDetails({ exams: [ - createExam({ id: "kyu5", labelKey: "chooser.button.kyu5" }), - createExam({ id: "kyu4", labelKey: "chooser.button.kyu4" }), + createExam({ + id: "kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, + }), + createExam({ + id: "kyu4", + label: { + type: "wellknown", + key: "kyu4", + }, + }), ], }), }); @@ -186,8 +239,20 @@ describe("Chooser.test.tsx", async () => { info: createDojoInfo({ id }), details: createDojoDetails({ exams: [ - createExam({ id: "kyu5", labelKey: "chooser.button.kyu5" }), - createExam({ id: "kyu4", labelKey: "chooser.button.kyu4" }), + createExam({ + id: "kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, + }), + createExam({ + id: "kyu4", + label: { + type: "wellknown", + key: "kyu4", + }, + }), ], }), }); @@ -211,7 +276,15 @@ describe("Chooser.test.tsx", async () => { it("renders filters", () => { const dojo = createResolvedDojo({ details: createDojoDetails({ - exams: [createExam({ id: "kyu5", labelKey: "chooser.button.kyu5" })], + exams: [ + createExam({ + id: "kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, + }), + ], }), }); renderSolid(() => ); @@ -225,7 +298,10 @@ describe("Chooser.test.tsx", async () => { exams: [ createExam({ id: "kyu5", - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "suwari waza": { "ai hanmi katate dori": { ikkyo: { omote: {} } } }, "hanmi handachi waza": { "ai hanmi katate dori": { ikkyo: { omote: {} } } }, diff --git a/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.tsx b/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.tsx index 73581a8..6ded3cf 100644 --- a/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.tsx +++ b/src/components/solid/organisms/TechniqueChooser/TechniqueChooser.tsx @@ -1,13 +1,13 @@ import { type Component, createDeferred, createSignal } from "solid-js"; import type { ResolvedDojo } from "$core/model/Dojo.ts"; -import { t } from "@/i18n"; +import { t, tx } from "@/i18n"; import { l } from "astro-i18n"; import { ExamSheet } from "@/components/solid/organisms/TechniqueChooser/ExamSheet.tsx"; import { ChooserControlButtons, type Option, } from "@/components/solid/organisms/TechniqueChooser/ChooserControlButtons.tsx"; -import type { Exam } from "$core/model"; +import type { Exam, ExamLabel, WellKnownExam } from "$core/model"; import { syncToStorage } from "@/components/solid/hooks/syncToStorage.ts"; import { isServer } from "solid-js/web"; import { ChooserControlContainer } from "@/components/solid/organisms/TechniqueChooser/ChooserControlContainer.tsx"; @@ -21,6 +21,7 @@ import { SimpleButton } from "@/components/solid/atoms/SimpleButton.tsx"; import { IconPrint, IconSpeak } from "@/icons"; import { createTechniqueStore } from "$core/store"; import { LinkButton } from "@/components/solid/atoms/LinkButton.tsx"; +import type { TranslationSchema } from "@/i18n/TranslationSchema.ts"; export const TechniqueChooser: Component<{ dojo: ResolvedDojo }> = (props) => { const [examSelection, setExamSelection] = syncToStorage(createSignal(new Set()), { @@ -115,5 +116,26 @@ export const TechniqueChooser: Component<{ dojo: ResolvedDojo }> = (props) => { }; function examsToOptions(exams: Exam[]): Option[] { - return exams.map((exam) => ({ id: exam.id, label: t(exam.labelKey) })); + return exams.map((exam) => ({ id: exam.id, label: resoveExamLabel(exam.label) })); +} + +const wellknownLabels: Record = { + kyu5: "chooser.button.kyu5", + kyu4: "chooser.button.kyu4", + kyu3: "chooser.button.kyu3", + kyu2: "chooser.button.kyu2", + kyu1: "chooser.button.kyu1", + dan1: "chooser.button.dan1", + dan2: "chooser.button.dan2", + dan3: "chooser.button.dan3", + dan4: "chooser.button.dan4", +}; + +function resoveExamLabel(label: ExamLabel): string { + switch (label.type) { + case "wellknown": + return t(wellknownLabels[label.key]); + case "free": + return tx(label.text); + } } diff --git a/src/core/model/Dojo.test-helper.ts b/src/core/model/Dojo.test-helper.ts index ba5847b..6395029 100644 --- a/src/core/model/Dojo.test-helper.ts +++ b/src/core/model/Dojo.test-helper.ts @@ -12,9 +12,24 @@ export function createDojoInfo(partialInfo: Partial = {}): DojoInfo { export function createExams() { return [ - createExam({ labelKey: "chooser.button.kyu5" }), - createExam({ labelKey: "chooser.button.kyu4" }), - createExam({ labelKey: "chooser.button.kyu3" }), + createExam({ + label: { + type: "wellknown", + key: "kyu5", + }, + }), + createExam({ + label: { + type: "wellknown", + key: "kyu4", + }, + }), + createExam({ + label: { + type: "wellknown", + key: "kyu3", + }, + }), ]; } diff --git a/src/core/model/Exam.test-helper.ts b/src/core/model/Exam.test-helper.ts index 32ba228..714eb40 100644 --- a/src/core/model/Exam.test-helper.ts +++ b/src/core/model/Exam.test-helper.ts @@ -4,7 +4,10 @@ import { nanoid } from "nanoid"; export function createExam(exam: Partial): Exam { return { id: nanoid(), - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: {}, ...exam, }; diff --git a/src/core/model/Exam.ts b/src/core/model/Exam.ts index fd30da6..54c9d6d 100644 --- a/src/core/model/Exam.ts +++ b/src/core/model/Exam.ts @@ -3,15 +3,25 @@ import type { TechniqueMetadata } from "./TechniqueMetadata"; import type { Defence } from "./Defence"; import type { Attack } from "./Attack"; import type { Execution } from "./Execution"; -import type { TranslationSchema } from "./TranslationSchema"; +import type { TranslatedText } from "$core/model/Dojo.ts"; export type Directions = Partial>; export type Defences = Partial>; export type Attacks = Partial>; export type Table = Partial>; +export type WellKnownExam = "kyu5" | "kyu4" | "kyu3" | "kyu2" | "kyu1" | "dan1" | "dan2" | "dan3" | "dan4"; +export type ExamLabel = + | { + type: "wellknown"; + key: WellKnownExam; + } + | { + type: "free"; + text: TranslatedText; + }; export interface Exam { id: string; - labelKey: keyof TranslationSchema; + label: ExamLabel; techniques: Table; } diff --git a/src/core/resolveExamTables/resolveExamTables.test.ts b/src/core/resolveExamTables/resolveExamTables.test.ts index 867161a..7291a99 100644 --- a/src/core/resolveExamTables/resolveExamTables.test.ts +++ b/src/core/resolveExamTables/resolveExamTables.test.ts @@ -6,7 +6,10 @@ describe("resolve-exam-tables", () => { const actual = resolveExamTables([ { id: "dan2", - labelKey: "chooser.button.dan2", + label: { + type: "wellknown", + key: "dan2", + }, techniques: { "suwari waza": { "ryote dori": { @@ -23,7 +26,10 @@ describe("resolve-exam-tables", () => { }, { id: "dan3", - labelKey: "chooser.button.dan3", + label: { + type: "wellknown", + key: "dan3", + }, techniques: { "tachi waza": { "ai hanmi katate dori": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/additional.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/additional.ts index 3775869..9421a4b 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/additional.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/additional.ts @@ -2,7 +2,12 @@ import type { Exam } from "$core/model"; export const additional: Exam = { id: "additional", - labelKey: "chooser.button.additional", + label: { + type: "free", + text: { + en: "+X", + }, + }, techniques: { "tachi waza": { "chudan tsuki": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/dan1.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/dan1.ts index 585d567..2038d88 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/dan1.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/dan1.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const dan1: Exam = { id: "dan1", - labelKey: "chooser.button.dan1", + label: { + type: "wellknown", + key: "dan1", + }, techniques: { "hanmi handachi waza": { "gyuako hanmi katate dori": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu1.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu1.ts index 6f787cc..00f13ef 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu1.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu1.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const kyu1: Exam = { id: "kyu1", - labelKey: "chooser.button.kyu1", + label: { + type: "wellknown", + key: "kyu1", + }, techniques: { "suwari waza": { "yokomen uchi": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu2.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu2.ts index b4abc64..c3a61e8 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu2.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu2.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model/Exam"; export const kyu2: Exam = { id: "kyu2", - labelKey: "chooser.button.kyu2", + label: { + type: "wellknown", + key: "kyu2", + }, techniques: { "suwari waza": { "yokomen uchi": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu3.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu3.ts index c7e62f1..0cb59f7 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu3.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu3.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model/Exam"; export const kyu3: Exam = { id: "kyu3", - labelKey: "chooser.button.kyu3", + label: { + type: "wellknown", + key: "kyu3", + }, techniques: { "suwari waza": { "gyuako hanmi katate dori": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu4.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu4.ts index 26dcb82..9f39cf1 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu4.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu4.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model/Exam"; export const kyu4: Exam = { id: "kyu4", - labelKey: "chooser.button.kyu4", + label: { + type: "wellknown", + key: "kyu4", + }, techniques: { "suwari waza": { "ai hanmi katate dori": { diff --git a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu5.ts b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu5.ts index 984692a..72d2445 100644 --- a/src/data/dojos/aikido-dojo-darmstadt/exams/kyu5.ts +++ b/src/data/dojos/aikido-dojo-darmstadt/exams/kyu5.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model/Exam"; export const kyu5: Exam = { id: "kyu5", - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "suwari waza": { "ryote dori": { diff --git a/src/data/dojos/aikido-foederation/exams/additional.ts b/src/data/dojos/aikido-foederation/exams/additional.ts index 6a01b25..cf27fa6 100644 --- a/src/data/dojos/aikido-foederation/exams/additional.ts +++ b/src/data/dojos/aikido-foederation/exams/additional.ts @@ -2,7 +2,12 @@ import type { Exam } from "$core/model"; export const additional: Exam = { id: "additional", - labelKey: "chooser.button.additional", + label: { + type: "free", + text: { + en: "+X", + }, + }, techniques: { "tachi waza": { "chudan tsuki": { diff --git a/src/data/dojos/aikido-foederation/exams/dan1.ts b/src/data/dojos/aikido-foederation/exams/dan1.ts index 6db04e0..2b4068f 100644 --- a/src/data/dojos/aikido-foederation/exams/dan1.ts +++ b/src/data/dojos/aikido-foederation/exams/dan1.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const dan1: Exam = { id: "dan1", - labelKey: "chooser.button.dan1", + label: { + type: "wellknown", + key: "dan1", + }, techniques: { "tanto dori": { "chudan tsuki": { diff --git a/src/data/dojos/aikido-foederation/exams/dan2.ts b/src/data/dojos/aikido-foederation/exams/dan2.ts index 781243e..dd7a8b2 100644 --- a/src/data/dojos/aikido-foederation/exams/dan2.ts +++ b/src/data/dojos/aikido-foederation/exams/dan2.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const dan2: Exam = { id: "dan2", - labelKey: "chooser.button.dan2", + label: { + type: "wellknown", + key: "dan2", + }, techniques: { "jo dori": { "chudan tsuki": { diff --git a/src/data/dojos/aikido-foederation/exams/dan3.ts b/src/data/dojos/aikido-foederation/exams/dan3.ts index 978ebd4..d9b9151 100644 --- a/src/data/dojos/aikido-foederation/exams/dan3.ts +++ b/src/data/dojos/aikido-foederation/exams/dan3.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const dan3: Exam = { id: "dan3", - labelKey: "chooser.button.dan3", + label: { + type: "wellknown", + key: "dan3", + }, techniques: { "tachi dori": { "shomen uchi": { diff --git a/src/data/dojos/aikido-foederation/exams/kyu1.ts b/src/data/dojos/aikido-foederation/exams/kyu1.ts index 8d1a53a..f56dcce 100644 --- a/src/data/dojos/aikido-foederation/exams/kyu1.ts +++ b/src/data/dojos/aikido-foederation/exams/kyu1.ts @@ -2,7 +2,11 @@ import type { Exam } from "$core/model"; export const kyu1: Exam = { id: "kyu1", - labelKey: "chooser.button.kyu1", + label: { + type: "wellknown", + key: "kyu1", + }, + techniques: { "suwari waza": { "shomen uchi": { diff --git a/src/data/dojos/aikido-foederation/exams/kyu2.ts b/src/data/dojos/aikido-foederation/exams/kyu2.ts index 5a9181d..bae83db 100644 --- a/src/data/dojos/aikido-foederation/exams/kyu2.ts +++ b/src/data/dojos/aikido-foederation/exams/kyu2.ts @@ -2,7 +2,11 @@ import type { Exam } from "$core/model"; export const kyu2: Exam = { id: "kyu2", - labelKey: "chooser.button.kyu2", + label: { + type: "wellknown", + key: "kyu2", + }, + techniques: { "suwari waza": { "gyuako hanmi katate dori": { diff --git a/src/data/dojos/aikido-foederation/exams/kyu3.ts b/src/data/dojos/aikido-foederation/exams/kyu3.ts index 0ebebdb..5f6c068 100644 --- a/src/data/dojos/aikido-foederation/exams/kyu3.ts +++ b/src/data/dojos/aikido-foederation/exams/kyu3.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const kyu3: Exam = { id: "kyu3", - labelKey: "chooser.button.kyu3", + label: { + type: "wellknown", + key: "kyu3", + }, techniques: { "suwari waza": { "gyuako hanmi katate dori": { diff --git a/src/data/dojos/aikido-foederation/exams/kyu4.ts b/src/data/dojos/aikido-foederation/exams/kyu4.ts index 8c0267c..86ca732 100644 --- a/src/data/dojos/aikido-foederation/exams/kyu4.ts +++ b/src/data/dojos/aikido-foederation/exams/kyu4.ts @@ -2,7 +2,11 @@ import type { Exam } from "$core/model"; export const kyu4: Exam = { id: "kyu4", - labelKey: "chooser.button.kyu4", + label: { + type: "wellknown", + key: "kyu4", + }, + techniques: { "suwari waza": { "ai hanmi katate dori": { diff --git a/src/data/dojos/aikido-foederation/exams/kyu5.ts b/src/data/dojos/aikido-foederation/exams/kyu5.ts index 0e4a218..86f723c 100644 --- a/src/data/dojos/aikido-foederation/exams/kyu5.ts +++ b/src/data/dojos/aikido-foederation/exams/kyu5.ts @@ -2,7 +2,10 @@ import type { Exam } from "$core/model"; export const kyu5: Exam = { id: "kyu5", - labelKey: "chooser.button.kyu5", + label: { + type: "wellknown", + key: "kyu5", + }, techniques: { "suwari waza": { "ryote dori": { diff --git a/src/i18n/common/de.json b/src/i18n/common/de.json index b5aa92b..40b4570 100644 --- a/src/i18n/common/de.json +++ b/src/i18n/common/de.json @@ -3,10 +3,10 @@ "app.title": "Aikido-Prüfung", "button.play-video.label": "Video zeigen", "button.settings.youtube.label": "YouTube Videos anzeigen", - "chooser.button.additional": "+X", "chooser.button.dan1": "1.DAN", "chooser.button.dan2": "2.DAN", "chooser.button.dan3": "3.DAN", + "chooser.button.dan4": "4.DAN", "chooser.button.kyu1": "1.Kyu", "chooser.button.kyu2": "2.Kyu", "chooser.button.kyu3": "3.Kyu", diff --git a/src/i18n/common/en.json b/src/i18n/common/en.json index 22eee60..ce1a1be 100644 --- a/src/i18n/common/en.json +++ b/src/i18n/common/en.json @@ -3,10 +3,10 @@ "app.title": "Aikido Exam", "button.play-video.label": "Play video", "button.settings.youtube.label": "Enable YouTube videos", - "chooser.button.additional": "+X", "chooser.button.dan1": "1st DAN", "chooser.button.dan2": "2nd DAN", "chooser.button.dan3": "3rd DAN", + "chooser.button.dan4": "4th DAN", "chooser.button.kyu1": "1st Kyu", "chooser.button.kyu2": "2nd Kyu", "chooser.button.kyu3": "3rd Kyu",