feat: replace static labels with configurable values

This commit is contained in:
Mathias Schopmans
2024-03-04 13:34:47 +01:00
committed by Mathias Schopmans
parent 5ef0f07822
commit f7e36ddc9d
13 changed files with 52 additions and 177 deletions

1
.gitignore vendored
View File

@@ -19,7 +19,6 @@
/data/* /data/*
!/data/about.md !/data/about.md
!/data/config.json !/data/config.json
!/data/messages.json
# misc # misc
.DS_Store .DS_Store

View File

@@ -113,5 +113,21 @@
"href": "https://github.com/aoepeople", "href": "https://github.com/aoepeople",
"icon": "github" "icon": "github"
} }
] ],
"imprint": "https://www.aoe.com/en/imprint.html",
"labels": {
"title": "AOE Technology Radar",
"imprint": "Legal Information",
"quadrant": "Quadrant",
"quadrantOverview": "Quadrant Overview",
"zoomIn": "Zoom in",
"filterByTag": "Filter by Tag",
"footer": "AOE is a leading global provider of services for digital transformation and digital business models. AOE relies exclusively on established Enterprise Open Source technologies. This leads to innovative solutions, digital products and portals in agile software projects, and helps build long-lasting, strategic partnerships with our customers.",
"notUpdated": "This item was not updated in last three versions of the Radar. Should it have appeared in one of the more recent editions, there is a good chance it remains pertinent. However, if the item dates back further, its relevance may have diminished and our current evaluation could vary. Regrettably, our capacity to consistently revisit items from past Radar editions is limited.",
"notFound": "404 - Page not found",
"pageAbout": "How to use AOE Technology Radar?",
"pageOverview": "Technologies Overview",
"pageSearch": "Search",
"searchPlaceholder": "What are you looking for?"
}
} }

View File

@@ -1,127 +0,0 @@
{
"radarName": "AOE Technology Radar",
"footerFootnote": "AOE is a leading global provider of services for digital transformation and digital business models. AOE relies exclusively on established Enterprise Open Source technologies. This leads to innovative solutions, digital products and portals in agile software projects, and helps build long-lasting, strategic partnerships with our customers.",
"legalInformationLabel": "Legal Information",
"legalInformationLink": "https://www.aoe.com/en/imprint.html",
"pageHelp": {
"headlinePrefix": "How to use the",
"paragraphs": [
{
"headline": "Introduction",
"values": [
"Technology is moving fast and new technologies and innovations appear continuously.",
"It's essential for a development and technology company such as AOE to constantly improve and keep track with the latest useful innovations. It is important to openly look for innovations and new technologies and to question established technologies and methods every now and then.",
"But, it is also important to wisely choose which technologies to use in our daily work and in the different projects we are carrying out. As we all know: There is no silver bullet."
]
},
{
"headline": "What is the AOE Technology Radar",
"values": [
"The Tech Radar is an overview of different technologies - from languages, frameworks, tools and patterns to platforms - that we consider \"new or mentionable\". The radar therefore doesn't provide an overview of all established technologies - but it focuses on items that have recently gained in importance or changed."
]
},
{
"headline": "How it is created",
"values": [
"The items in the technology radar are raised by the different teams and therefore a lot of the items are related to the work and challenges the teams face in the different projects. In fact, we don't include anything on the radar, which we haven't already tried ourselves at least once.",
"There have been a lot of valuable discussions in different expert groups about the classification and details of each of technologies and innovations. And the result of all this can be found in the latest technology radar."
]
},
{
"headline": "How should it be used",
"values": [
"The radar acts as an overview of technologies that we think everyone in the teams should currently know about.",
"Its goal is to act as a guide and inspiration for the daily work in the teams. Its purpose is also to provide helpful information and a bird's-eye perspective - so that decisions can be taken with a much deeper understanding of the subject matter. This results in more-informed and better-aligned decisions.",
"We also hope that developers outside of AOE find the information in our technology overview inspirational.",
"We group or categorize the items in 4 quadrants - (sometimes, when it's not 100% clear where a item belongs, we choose the best fit)."
]
}
],
"quadrants": [
{
"description": "We've placed development languages (such as Scala or Golang) here, as well as more low-level development frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds.",
"name": "Languages and Frameworks"
},
{
"description": "Here we put different software tools - from small helpers to bigger software projects.",
"name": "Tools"
},
{
"description": "Patterns are so important, and a lot of them are valid for a long time (compared to some tools or frameworks). So, this is the category where we put information on methods and patterns concerning development, continuous x, testing, organization, architecture, etc.",
"name": "Methods and Patterns"
},
{
"description": "(including AOE internal Services): Here we include infrastructure platforms and services. We also use this category to communicate news about AOE services that we want all AOE teams to be aware of.",
"name": "Platforms and Operations"
}
],
"quadrantsPreDescription": "The quadrants are:",
"rings": [
{
"description": "We can clearly recommend this technology. We have used it for longer period of time in many teams and it has proven to be stable and useful.",
"name": "Adopt"
},
{
"description": "We have used it with success and recommend to have a closer look at the technology in this ring. The goal of items here is to look at them more closely, with the goal to bring them to the adopt level.",
"name": "Trial"
},
{
"description": "We have tried it out and we find it promising. We recommend having a look at these items when you face a specific need for the technology in your project.",
"name": "Assess"
},
{
"description": "This category is a bit special. Unlike the others, we recommend to stop doing or using something. That does not mean that they are bad and it often might be ok to use them in existing projects. But we move things here if we think we shouldn't do them anymore - because we see better options or alternatives now.",
"name": "Hold"
}
],
"ringsPreDescription": "Each of the items is classified in one of these rings:",
"sourcecodeLink": {
"description": "Contributions and source code of the AOE Tech Radar are on github:",
"href": "https://github.com/AOEpeople/aoe_technology_radar",
"name": "AOE Tech Radar on Github"
}
},
"pageIndex": {
"publishedLabel": "Quadrant Overview"
},
"pageItem": {
"quadrantOverview": "Quadrant Overview"
},
"pageOverview": {
"title": "Technologies Overview"
},
"revisionsText": "Revisions:",
"searchLabel": "Search",
"searchPlaceholder": "What are you looking for?",
"socialLinks": [
{
"href": "https://www.facebook.com/aoepeople",
"iconName": "facebook"
},
{
"href": "https://twitter.com/aoepeople",
"iconName": "twitter"
},
{
"href": "https://www.linkedin.com/company/aoe",
"iconName": "linkedIn"
},
{
"href": "https://www.xing.com/company/aoe",
"iconName": "xing"
},
{
"href": "https://www.instagram.com/aoepeople",
"iconName": "instagram"
},
{
"href": "https://www.youtube.com/user/aoepeople",
"iconName": "youtube"
},
{
"href": "https://github.com/aoepeople",
"iconName": "github"
}
],
"socialLinksLabel": "Follow us:"
}

View File

@@ -3,10 +3,13 @@ import { ChangeEvent, useEffect, useState } from "react";
import Search from "../Icons/Search"; import Search from "../Icons/Search";
import styles from "./QueryFilter.module.css"; import styles from "./QueryFilter.module.css";
import { getLabel } from "@/lib/data";
interface QueryFilterProps { interface QueryFilterProps {
value?: string; value?: string;
onChange: (value: string) => void; onChange: (value: string) => void;
} }
export function QueryFilter({ value, onChange }: QueryFilterProps) { export function QueryFilter({ value, onChange }: QueryFilterProps) {
const [val, setVal] = useState(value); const [val, setVal] = useState(value);
const _onChange = (e: ChangeEvent<HTMLInputElement>) => { const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
@@ -24,6 +27,7 @@ export function QueryFilter({ value, onChange }: QueryFilterProps) {
className={styles.input} className={styles.input}
id="search" id="search"
type="search" type="search"
placeholder={getLabel("searchPlaceholder")}
value={val} value={val}
onChange={_onChange} onChange={_onChange}
/> />

View File

@@ -2,17 +2,14 @@ import logo from "../../../public/logo.svg";
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
import { SocialLinks } from "@/components/SocialLinks/SocialLinks"; import { SocialLinks } from "@/components/SocialLinks/SocialLinks";
import { getAppName, getMessages } from "@/lib/data"; import { getAppName, getLabel } from "@/lib/data";
export function Footer() { export function Footer() {
const appName = getAppName();
const { footerFootnote } = getMessages();
return ( return (
<div className={styles.footer}> <div className={styles.footer}>
<div className={styles.branding}> <div className={styles.branding}>
<img src={logo.src} className={styles.logo} alt={appName} /> <img src={logo.src} className={styles.logo} alt={getAppName()} />
<p className={styles.description}>{footerFootnote}</p> <p className={styles.description}>{getLabel("footer")}</p>
<SocialLinks className={styles.socialLinks} /> <SocialLinks className={styles.socialLinks} />
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@ import styles from "./ItemDetail.module.css";
import { RingBadge } from "@/components/Badge/Badge"; import { RingBadge } from "@/components/Badge/Badge";
import Attention from "@/components/Icons/Attention"; import Attention from "@/components/Icons/Attention";
import { Tag } from "@/components/Tags/Tags"; import { Tag } from "@/components/Tags/Tags";
import { getReleases } from "@/lib/data"; import { getLabel, getReleases } from "@/lib/data";
import { Item } from "@/lib/types"; import { Item } from "@/lib/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -18,6 +18,7 @@ interface ItemProps {
} }
export function ItemDetail({ item }: ItemProps) { export function ItemDetail({ item }: ItemProps) {
const notMaintainedText = getLabel("notUpdated");
return ( return (
<> <>
<div className={styles.header}> <div className={styles.header}>
@@ -27,19 +28,12 @@ export function ItemDetail({ item }: ItemProps) {
))} ))}
</div> </div>
<div className={styles.revisions}> <div className={styles.revisions}>
{isNotMaintained(item.release) && ( {notMaintainedText && isNotMaintained(item.release) && (
<div className={cn(styles.revision, styles.hint)}> <div className={cn(styles.revision, styles.hint)}>
<span className={styles.release}> <span className={styles.release}>
<Attention className={styles.icon} /> <Attention className={styles.icon} />
</span> </span>
<div className={styles.content}> <div className={styles.content}>{notMaintainedText}</div>
This item was not updated in last three versions of the Radar.
Should it have appeared in one of the more recent editions, there
is a good chance it remains pertinent. However, if the item dates
back further, its relevance may have diminished and our current
evaluation could vary. Regrettably, our capacity to consistently
revisit items from past Radar editions is limited.
</div>
</div> </div>
)} )}
<Revision release={item.release} ring={item.ring} body={item.body} /> <Revision release={item.release} ring={item.ring} body={item.body} />

View File

@@ -5,7 +5,7 @@ import styles from "./Navigation.module.css";
import IconOverview from "@/components/Icons/Overview"; import IconOverview from "@/components/Icons/Overview";
import IconQuestion from "@/components/Icons/Question"; import IconQuestion from "@/components/Icons/Question";
import IconSearch from "@/components/Icons/Search"; import IconSearch from "@/components/Icons/Search";
import { getAppName } from "@/lib/data"; import { getAppName, getLabel } from "@/lib/data";
export function Navigation() { export function Navigation() {
return ( return (
@@ -14,36 +14,19 @@ export function Navigation() {
<li className={styles.item}> <li className={styles.item}>
<Link href="/help-and-about-tech-radar"> <Link href="/help-and-about-tech-radar">
<IconQuestion className={styles.icon} /> <IconQuestion className={styles.icon} />
<span className={styles.label}>How to use {getAppName()}?</span> <span className={styles.label}>{getLabel("pageAbout")}</span>
</Link> </Link>
</li> </li>
{/*
<li className={styles.item}>
<Dialog>
<DialogTrigger asChild>
<a href="#">
<IconFilter className={styles.icon} />
<span className={styles.label}>Filter</span>
</a>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Filter by Tags</DialogTitle>
</DialogHeader>
</DialogContent>
</Dialog>
</li>
*/}
<li className={styles.item}> <li className={styles.item}>
<Link href="/overview"> <Link href="/overview">
<IconOverview className={styles.icon} /> <IconOverview className={styles.icon} />
<span className={styles.label}>Technologies Overview</span> <span className={styles.label}>{getLabel("pageOverview")}</span>
</Link> </Link>
</li> </li>
<li className={styles.item}> <li className={styles.item}>
<Link href="/overview"> <Link href="/overview">
<IconSearch className={styles.icon} /> <IconSearch className={styles.icon} />
<span className={styles.label}>Search</span> <span className={styles.label}>{getLabel("pageSearch")}</span>
</Link> </Link>
</li> </li>
</ul> </ul>

View File

@@ -3,6 +3,7 @@ import Link from "next/link";
import styles from "./QuadrantLink.module.css"; import styles from "./QuadrantLink.module.css";
import Pie from "@/components/Icons/Pie"; import Pie from "@/components/Icons/Pie";
import { getLabel } from "@/lib/data";
import { Quadrant } from "@/lib/types"; import { Quadrant } from "@/lib/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -13,7 +14,7 @@ interface QuadrantLinkProps {
} }
export function QuadrantLink({ export function QuadrantLink({
quadrant, quadrant,
label = "Zoom in", label = getLabel("zoomIn"),
className, className,
}: QuadrantLinkProps) { }: QuadrantLinkProps) {
return ( return (

View File

@@ -4,6 +4,7 @@ import { CSSProperties, useMemo } from "react";
import styles from "./Label.module.css"; import styles from "./Label.module.css";
import { QuadrantLink } from "@/components/QuadrantLink/QuadrantLink"; import { QuadrantLink } from "@/components/QuadrantLink/QuadrantLink";
import { getLabel } from "@/lib/data";
import { Quadrant } from "@/lib/types"; import { Quadrant } from "@/lib/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -23,7 +24,9 @@ export function Label({ quadrant }: LabelProps) {
style={style} style={style}
> >
<div className={styles.header}> <div className={styles.header}>
<span>Quadrant {quadrant.position}</span> <span>
{getLabel("quadrant")} {quadrant.position}
</span>
<QuadrantLink quadrant={quadrant} /> <QuadrantLink quadrant={quadrant} />
</div> </div>
<h3 className={styles.title}>{quadrant.title}</h3> <h3 className={styles.title}>{quadrant.title}</h3>

View File

@@ -5,6 +5,7 @@ import styles from "./Tags.module.css";
import IconRemove from "@/components/Icons/Close"; import IconRemove from "@/components/Icons/Close";
import IconTag from "@/components/Icons/Tag"; import IconTag from "@/components/Icons/Tag";
import { getLabel } from "@/lib/data";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type TagProps = { type TagProps = {
@@ -36,7 +37,7 @@ interface TagsProps {
export function Tags({ tags, activeTag, className }: TagsProps) { export function Tags({ tags, activeTag, className }: TagsProps) {
return ( return (
<div className={cn(styles.tags, className)}> <div className={cn(styles.tags, className)}>
<h3>Filter by Tag</h3> <h3>{getLabel("filterByTag")}</h3>
{tags.map((tag) => ( {tags.map((tag) => (
<Tag key={tag} tag={tag} isActive={activeTag == tag} scroll={false} /> <Tag key={tag} tag={tag} isActive={activeTag == tag} scroll={false} />
))} ))}

View File

@@ -1,15 +1,14 @@
import config from "../../data/config.json"; import config from "../../data/config.json";
import data from "../../data/data.json"; import data from "../../data/data.json";
import messages from "../../data/messages.json";
import { Flag, Item, Quadrant, Ring } from "@/lib/types"; import { Flag, Item, Quadrant, Ring } from "@/lib/types";
export function getMessages() { export function getLabel(key: keyof typeof config.labels) {
return messages; return config.labels[key] || "";
} }
export function getAppName() { export function getAppName() {
return messages.radarName; return getLabel("title");
} }
export function getChartConfig() { export function getChartConfig() {

View File

@@ -11,6 +11,7 @@ import { QuadrantLink } from "@/components/QuadrantLink/QuadrantLink";
import { import {
getItem, getItem,
getItems, getItems,
getLabel,
getQuadrant, getQuadrant,
sortByFeaturedAndTitle, sortByFeaturedAndTitle,
} from "@/lib/data"; } from "@/lib/data";
@@ -45,7 +46,10 @@ const ItemPage: CustomPage = () => {
<h3>{quadrant.title}</h3> <h3>{quadrant.title}</h3>
<div className={styles.ringAndQuadrant}> <div className={styles.ringAndQuadrant}>
<RingBadge ring={item.ring} /> <RingBadge ring={item.ring} />
<QuadrantLink quadrant={quadrant} label="Quadrant Overview" /> <QuadrantLink
quadrant={quadrant}
label={getLabel("quadrantOverview")}
/>
</div> </div>
<ItemList items={relatedItems} activeId={item.id} /> <ItemList items={relatedItems} activeId={item.id} />

View File

@@ -5,11 +5,12 @@ import { useCallback, useMemo } from "react";
import { Filter } from "@/components/Filter/Filter"; import { Filter } from "@/components/Filter/Filter";
import { ItemList } from "@/components/ItemList/ItemList"; import { ItemList } from "@/components/ItemList/ItemList";
import { getItems } from "@/lib/data"; import { getItems, getLabel } from "@/lib/data";
import { formatTitle } from "@/lib/format"; import { formatTitle } from "@/lib/format";
import { CustomPage } from "@/pages/_app"; import { CustomPage } from "@/pages/_app";
const Overview: CustomPage = () => { const Overview: CustomPage = () => {
const title = getLabel("pageOverview");
const router = useRouter(); const router = useRouter();
const ring = router.query.ring as string | undefined; const ring = router.query.ring as string | undefined;
const query = (router.query.query as string) || ""; const query = (router.query.query as string) || "";
@@ -65,10 +66,10 @@ const Overview: CustomPage = () => {
return ( return (
<> <>
<Head> <Head>
<title>{formatTitle("Technologies Overview")}</title> <title>{formatTitle(title)}</title>
</Head> </Head>
<h1>Technologies Overview</h1> <h1>{title}</h1>
<Filter <Filter
query={query} query={query}
ring={ring} ring={ring}