From 0c37cb07df8c3ff288e177b25ec32de138348e31 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sun, 14 Dec 2025 17:36:20 +0900 Subject: [PATCH 1/6] feat: create criteria json file --- .../locals/en/world-heritage-criteria.json | 42 +++++++++++++++++++ .../locals/ja/world-heritage-criteria.json | 42 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 client/src/locals/en/world-heritage-criteria.json create mode 100644 client/src/locals/ja/world-heritage-criteria.json diff --git a/client/src/locals/en/world-heritage-criteria.json b/client/src/locals/en/world-heritage-criteria.json new file mode 100644 index 0000000..458d1f1 --- /dev/null +++ b/client/src/locals/en/world-heritage-criteria.json @@ -0,0 +1,42 @@ +{ + "i": { + "title": "Masterpiece", + "description": "Represents a masterpiece of human creative genius." + }, + "ii": { + "title": "Interchange of values", + "description": "Shows an important interchange of human values over time or within a cultural area." + }, + "iii": { + "title": "Unique testimony", + "description": "Bears a unique or exceptional testimony to a cultural tradition or civilization." + }, + "iv": { + "title": "Architectural ensemble", + "description": "An outstanding example of a type of building, architectural or technological ensemble, or landscape illustrating a significant stage in human history." + }, + "v": { + "title": "Traditional human settlement", + "description": "An outstanding example of traditional human settlement or land-use representative of a culture." + }, + "vi": { + "title": "Associative significance", + "description": "Directly associated with events, living traditions, ideas, beliefs, or artistic works of outstanding universal significance." + }, + "vii": { + "title": "Natural beauty", + "description": "Contains superlative natural phenomena or areas of exceptional natural beauty." + }, + "viii": { + "title": "Earth’s history", + "description": "Outstanding examples representing major stages of Earth’s history, including geological processes." + }, + "ix": { + "title": "Ecological processes", + "description": "Outstanding examples representing significant ongoing ecological and biological processes." + }, + "x": { + "title": "Biodiversity", + "description": "Contains important natural habitats for in-situ conservation of biological diversity." + } +} diff --git a/client/src/locals/ja/world-heritage-criteria.json b/client/src/locals/ja/world-heritage-criteria.json new file mode 100644 index 0000000..f9bd9da --- /dev/null +++ b/client/src/locals/ja/world-heritage-criteria.json @@ -0,0 +1,42 @@ +{ + "i": { + "title": "人類の創造的才能の傑作", + "description": "人類の創造的才能を示す傑作であること。" + }, + "ii": { + "title": "価値観の交流", + "description": "建築、技術、芸術、都市計画などにおける人類の価値観の重要な交流を示すこと。" + }, + "iii": { + "title": "文明の証拠", + "description": "現存する、または消滅した文化的伝統や文明の独自または稀有な証拠であること。" + }, + "iv": { + "title": "建築・技術の顕著な例", + "description": "人類の歴史における重要な段階を示す建築様式や技術的集合体の顕著な例であること。" + }, + "v": { + "title": "伝統的居住形態", + "description": "ある文化を代表する伝統的な人間の居住形態や土地利用の顕著な例であること。" + }, + "vi": { + "title": "思想・信仰との関連", + "description": "顕著な普遍的価値をもつ出来事、思想、信仰、芸術作品と直接関連していること。" + }, + "vii": { + "title": "自然美", + "description": "卓越した自然美や美的価値をもつ自然現象または地域を含むこと。" + }, + "viii": { + "title": "地球の歴史", + "description": "地球の歴史や地質学的過程を示す顕著な例を含むこと。" + }, + "ix": { + "title": "生態学的過程", + "description": "生態系や生物進化における重要な進行中の過程を示す顕著な例であること。" + }, + "x": { + "title": "生物多様性", + "description": "生物多様性の保全にとって重要な自然生息地を含むこと。" + } +} From e52f4b7e50e25a5519f05e2c9591daeab51033b9 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Sun, 14 Dec 2025 22:35:01 +0900 Subject: [PATCH 2/6] feat: create criteria domain & UI --- client/src/domain/criteria.ts | 48 ++++++++++++++++++++++++++ client/src/shared/uis/CriteriaTags.tsx | 45 ++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 client/src/domain/criteria.ts create mode 100644 client/src/shared/uis/CriteriaTags.tsx diff --git a/client/src/domain/criteria.ts b/client/src/domain/criteria.ts new file mode 100644 index 0000000..847f3ea --- /dev/null +++ b/client/src/domain/criteria.ts @@ -0,0 +1,48 @@ +import en from "../locals/en/world-heritage-criteria.json"; +import ja from "../locals/ja/world-heritage-criteria.json"; + +export type CriteriaText = { + title: string; + description: string; +}; + +export const CRITERIA_CODES = [ + "i", + "ii", + "iii", + "iv", + "v", + "vi", + "vii", + "viii", + "ix", + "x", +] as const; + +export type CriteriaCode = (typeof CRITERIA_CODES)[number]; +export type CriteriaDict = Record; + +export const LOCALES = ["en", "ja"] as const; +export type Locale = (typeof LOCALES)[number]; + +export const UNESCO_CRITERIA_SOURCE_URL = "https://whc.unesco.org/en/criteria/"; +const CRITERIA_BY_LOCALE: Record = { + en: en as CriteriaDict, + ja: ja as CriteriaDict, +}; + +export const getCriteriaDict = (locale: Locale): CriteriaDict => CRITERIA_BY_LOCALE[locale]; +export const getCriteria = (code: CriteriaCode, locale: Locale): CriteriaText => { + return CRITERIA_BY_LOCALE[locale][code]; +}; + +export const getCriteriaSafe = (code: CriteriaCode, locale: string): CriteriaText => { + const safeLocale: Locale = (LOCALES as readonly string[]).includes(locale) + ? (locale as Locale) + : "en"; + return CRITERIA_BY_LOCALE[safeLocale][code]; +}; + +export const isCriteriaCode = (v: string): v is CriteriaCode => { + return (CRITERIA_CODES as readonly string[]).includes(v); +}; diff --git a/client/src/shared/uis/CriteriaTags.tsx b/client/src/shared/uis/CriteriaTags.tsx new file mode 100644 index 0000000..7c193a1 --- /dev/null +++ b/client/src/shared/uis/CriteriaTags.tsx @@ -0,0 +1,45 @@ +import { + getCriteria, + isCriteriaCode, + UNESCO_CRITERIA_SOURCE_URL, + type CriteriaCode, + type Locale, +} from "../../domain/criteria"; + +type Props = { + // DB/APIがCriteriaCode[]で保証されてるなら CriteriaCode[] のままでOK + // もし実態が string[] なら string[] に変えて isCriteriaCode で絞る + criteria: Array; + locale: Locale; +}; + +export function CriteriaTags({ criteria, locale }: Props) { + const safeCriteria = criteria.filter((c): c is CriteriaCode => + typeof c === "string" ? isCriteriaCode(c) : false, + ); + + return ( + + ); +} From e03ef921f131d7da6e8e3d12417739830154fba6 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 15 Dec 2025 14:43:03 +0900 Subject: [PATCH 3/6] fix: delete not using comments --- client/src/shared/uis/CriteriaTags.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/shared/uis/CriteriaTags.tsx b/client/src/shared/uis/CriteriaTags.tsx index 7c193a1..da416d8 100644 --- a/client/src/shared/uis/CriteriaTags.tsx +++ b/client/src/shared/uis/CriteriaTags.tsx @@ -7,8 +7,6 @@ import { } from "../../domain/criteria"; type Props = { - // DB/APIがCriteriaCode[]で保証されてるなら CriteriaCode[] のままでOK - // もし実態が string[] なら string[] に変えて isCriteriaCode で絞る criteria: Array; locale: Locale; }; From b737ae6bc6b3fb4edc9ae97a99853ee0a11b3918 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 15 Dec 2025 22:37:20 +0900 Subject: [PATCH 4/6] feat: criteria content changed --- client/src/domain/criteria.ts | 9 ++++++- client/src/shared/uis/CriteriaTags.tsx | 35 +++++++++++++------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/client/src/domain/criteria.ts b/client/src/domain/criteria.ts index 847f3ea..300f478 100644 --- a/client/src/domain/criteria.ts +++ b/client/src/domain/criteria.ts @@ -32,8 +32,15 @@ const CRITERIA_BY_LOCALE: Record = { }; export const getCriteriaDict = (locale: Locale): CriteriaDict => CRITERIA_BY_LOCALE[locale]; + export const getCriteria = (code: CriteriaCode, locale: Locale): CriteriaText => { - return CRITERIA_BY_LOCALE[locale][code]; + const entry = CRITERIA_BY_LOCALE[locale]?.[code]; + if (entry) return entry; + + const fallback = CRITERIA_BY_LOCALE.en?.[code]; + if (fallback) return fallback; + + return { title: code, description: "" }; }; export const getCriteriaSafe = (code: CriteriaCode, locale: string): CriteriaText => { diff --git a/client/src/shared/uis/CriteriaTags.tsx b/client/src/shared/uis/CriteriaTags.tsx index da416d8..ea5576c 100644 --- a/client/src/shared/uis/CriteriaTags.tsx +++ b/client/src/shared/uis/CriteriaTags.tsx @@ -7,37 +7,38 @@ import { } from "../../domain/criteria"; type Props = { - criteria: Array; + criteria: readonly (CriteriaCode | string)[]; locale: Locale; }; export function CriteriaTags({ criteria, locale }: Props) { - const safeCriteria = criteria.filter((c): c is CriteriaCode => - typeof c === "string" ? isCriteriaCode(c) : false, - ); + const safeCriteria = criteria + .map((criterion) => String(criterion).trim().toLowerCase()) + .filter((criterion): criterion is CriteriaCode => isCriteriaCode(criterion)); + + if (safeCriteria.length === 0) return null; return ( ); } From e657bc9f6ce0320272ca2a8e503eec103d44ed14 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 15 Dec 2025 22:38:41 +0900 Subject: [PATCH 5/6] fix: change criteria display UI --- .../heritage-detail/HeritageDetailLayout.tsx | 10 ++-- .../heritage-detail/HeritageHero.tsx | 7 ++- .../HeritageOverviewSection.tsx | 50 +++++++++++++------ .../heritage-detail/HeritageSidebar.tsx | 32 +++++++----- .../heritage-detail/heritage-detail.css | 47 +++++++++++++++++ 5 files changed, 113 insertions(+), 33 deletions(-) diff --git a/client/src/app/features/top/components/heritage-detail/HeritageDetailLayout.tsx b/client/src/app/features/top/components/heritage-detail/HeritageDetailLayout.tsx index 6e77378..4947dde 100644 --- a/client/src/app/features/top/components/heritage-detail/HeritageDetailLayout.tsx +++ b/client/src/app/features/top/components/heritage-detail/HeritageDetailLayout.tsx @@ -4,23 +4,25 @@ import { HeritageHero } from "./HeritageHero"; import { HeritageOverviewSection } from "./HeritageOverviewSection.tsx"; import { HeritageSidebar } from "./HeritageSidebar.tsx"; import { HeritageGallery } from "./HeritageGallery"; +import type { Locale } from "../../../../../domain/criteria.ts"; type Props = { item: WorldHeritageDetailVm; + locale: Locale; }; -export function HeritageDetailLayout({ item }: Props) { +export function HeritageDetailLayout({ item, locale }: Props) { return (
- +
- +
- +
); diff --git a/client/src/app/features/top/components/heritage-detail/HeritageHero.tsx b/client/src/app/features/top/components/heritage-detail/HeritageHero.tsx index 3e26013..dec23f6 100644 --- a/client/src/app/features/top/components/heritage-detail/HeritageHero.tsx +++ b/client/src/app/features/top/components/heritage-detail/HeritageHero.tsx @@ -1,12 +1,15 @@ import type { WorldHeritageDetailVm, WorldHeritageImageVm } from "../../types"; import { HeroImage } from "./HeroImage"; import "./heritage-detail.css"; +import { CriteriaTags } from "@shared/uis/CriteriaTags.tsx"; +import type { Locale } from "../../../../../domain/criteria.ts"; type Props = { item: WorldHeritageDetailVm; + locale: Locale; }; -export function HeritageHero({ item }: Props) { +export function HeritageHero({ item, locale }: Props) { const primaryImage: WorldHeritageImageVm | undefined = item.images.find((img) => img.isPrimary) ?? item.images[0]; @@ -20,7 +23,7 @@ export function HeritageHero({ item }: Props) {

{item.subtitle}

- {item.category} + {item.stateParty && {item.stateParty}} {item.yearInscribed && ( Inscribed: {item.yearInscribed} diff --git a/client/src/app/features/top/components/heritage-detail/HeritageOverviewSection.tsx b/client/src/app/features/top/components/heritage-detail/HeritageOverviewSection.tsx index d77ea15..835f00f 100644 --- a/client/src/app/features/top/components/heritage-detail/HeritageOverviewSection.tsx +++ b/client/src/app/features/top/components/heritage-detail/HeritageOverviewSection.tsx @@ -1,42 +1,64 @@ import type { WorldHeritageDetailVm } from "../../types"; import "./heritage-detail.css"; import { HeritageMetadataList } from "./HeritageMetadataList"; +import { CriteriaTags } from "@shared/uis/CriteriaTags.tsx"; +import type { Locale } from "../../../../../domain/criteria.ts"; type Props = { item: WorldHeritageDetailVm; + locale: Locale; }; -export function HeritageOverviewSection({ item }: Props) { +export function HeritageOverviewSection({ item, locale }: Props) { return (

Overview

-

{item.shortDescription}

+ {item.shortDescription && ( +

{item.shortDescription}

+ )} + +
+

+ Criteria +

-
-

Criteria

-

{item.criteriaText || "—"}

-
+
+ {item.criteria?.length ? ( + + ) : ( + — + )} +
+
+ +
+

+ Area & Buffer zone +

-
-

Area & Buffer zone

-
+
{item.isEndangered && ( -
-

Danger status

+
+

+ Danger status +

This property is inscribed on the List of World Heritage in Danger.

-
+
)} ); diff --git a/client/src/app/features/top/components/heritage-detail/HeritageSidebar.tsx b/client/src/app/features/top/components/heritage-detail/HeritageSidebar.tsx index 3e85d12..7e36855 100644 --- a/client/src/app/features/top/components/heritage-detail/HeritageSidebar.tsx +++ b/client/src/app/features/top/components/heritage-detail/HeritageSidebar.tsx @@ -1,35 +1,38 @@ import type { WorldHeritageDetailVm } from "../../types"; import "./heritage-detail.css"; import { HeritageMetadataList } from "./HeritageMetadataList"; +import type { Locale } from "../../../../../domain/criteria"; type Props = { item: WorldHeritageDetailVm; + locale: Locale; }; +const formatCriteriaInline = (criteria: string[] | undefined) => + criteria?.length ? criteria.map((c) => `(${c})`).join("") : "—"; + export function HeritageSidebar({ item }: Props) { - const items = [ - { label: "Country", value: item.country }, - { label: "Region", value: item.region }, - { label: "Year of inscription", value: item.yearInscribed }, - { label: "Criteria", value: item.criteriaText || "—" }, - { label: "Area", value: item.areaText }, - { label: "Buffer zone", value: item.bufferText }, + const facts = [ + { label: "Country", value: item.country ?? "—" }, + { label: "Date of inscription", value: item.yearInscribed ?? "—" }, + { label: "Criteria", value: formatCriteriaInline(item.criteria) }, + { label: "Property", value: item.areaText ?? "—" }, + { label: "Buffer zone", value: item.bufferText ?? "—" }, { label: "Coordinates", value: item.latitude != null && item.longitude != null - ? `${item.latitude.toFixed(4)}, ${item.longitude.toFixed(4)}` + ? `${item.latitude.toFixed(4)} ${item.longitude.toFixed(4)}` : "—", hidden: item.latitude == null && item.longitude == null, }, ] as const; return ( -