Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
926c695
feat(integration-framework): add strings subpath with I18nToken type
Medformatik May 27, 2026
7268800
feat(integration-framework): add I18nToken resolver
Medformatik May 27, 2026
324258f
feat(integration-framework): add sharedT typed constants + en/de shar…
Medformatik May 27, 2026
8a525d7
feat(api): ship frameworkStrings in /api/integrations response
Medformatik May 27, 2026
53af596
feat(web): add useDataSourceI18nResolver + FrameworkStringsProvider
Medformatik May 27, 2026
b69a7a2
refactor(web): replace ROW_LABEL_KEYS lookup with token resolver
Medformatik May 27, 2026
aaa935a
refactor(parking): emit I18nToken across data-source contract
Medformatik May 27, 2026
3138098
refactor(fuel): emit I18nToken across data-source contract
Medformatik May 27, 2026
a19f578
refactor(fuel): emit I18nToken across data-source contract
Medformatik May 27, 2026
0e59683
refactor(webcam): emit I18nToken across data-source contract
Medformatik May 27, 2026
e1c2b2c
refactor(bike-sharing): emit I18nToken across data-source contract
Medformatik May 27, 2026
d11979c
refactor(car-sharing): emit I18nToken across data-source contract
Medformatik May 27, 2026
67b6a00
refactor(scooter-sharing): emit I18nToken across data-source contract
Medformatik May 27, 2026
19254a7
refactor(integrations): final sweep — no English literals across data…
Medformatik May 27, 2026
ecea6da
feat(integration-framework)!: tighten data-source contract to I18nTok…
Medformatik May 28, 2026
cb4fe6d
chore(i18n): delete legacy dataSources.* keys migrated to integration…
Medformatik May 28, 2026
baa938d
chore(i18n): extend check-translations to cover framework + integrati…
Medformatik May 28, 2026
b150e7a
refactor(geocoding-db-ris): tighten StationDetailSection.title to I18…
Medformatik May 28, 2026
76ab902
refactor(integration-framework): drop write-only IntegrationRegistry.…
Medformatik May 28, 2026
010e8b1
fix(integration-framework): guard token resolver against malformed IC…
Medformatik May 28, 2026
21feb5e
refactor(integration-framework)!: enforce I18nToken row labels via mi…
Medformatik May 28, 2026
4fd3ca5
ci: grant statuses:write to PR title validation workflow
Medformatik May 28, 2026
c25ab0c
fix(i18n): restore dataSources.* keys still used by the filter/summar…
Medformatik May 28, 2026
2d2013d
fix(integration-framework): localize usageInfo + accessory lists via …
Medformatik May 28, 2026
c073778
fix(integration-framework): treat empty-string catalog values as miss…
Medformatik May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ on:
types: [opened, edited, synchronize, reopened]

permissions:
# `pull-requests: write` is needed because `wip: true` makes the action
# set the PR check to "pending" while the title contains [WIP].
# See: https://github.com/amannn/action-semantic-pull-request#wip
# `statuses: write` is required because action-semantic-pull-request@v6
# reports its result via the commit-statuses API (also used by `wip: true`
# to flag [WIP] titles). Without it the action fails with
# "Resource not accessible by integration". `pull-requests: write` lets it
# read the PR title under `pull_request_target`.
# See: https://github.com/amannn/action-semantic-pull-request#permissions
pull-requests: write
statuses: write

jobs:
validate:
Expand Down
16 changes: 10 additions & 6 deletions apps/api/src/integration-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
integrationBackendBundlePath,
integrationFrontendBundlePath,
} from "@openmapx/integration-framework/installer";
import { sharedStrings } from "@openmapx/integration-framework/strings";
import { registerPoiSources as registerPoiSourcesInStore } from "@openmapx/poi-source-registry";
import { eq } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
Expand Down Expand Up @@ -1059,12 +1060,15 @@ export async function initIntegrations(

// Register the /api/integrations endpoint
fastify.get("/api/integrations", async () => {
return Array.from(integrations.values())
.filter((i) => i.enabled)
.map((i) => ({
...toIntegrationMeta(i),
isBuiltIn: i.isBuiltIn,
}));
return {
integrations: Array.from(integrations.values())
.filter((i) => i.enabled)
.map((i) => ({
...toIntegrationMeta(i),
isBuiltIn: i.isBuiltIn,
})),
frameworkStrings: sharedStrings,
};
});

// Serve community integration frontend bundles
Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@
"@mapbox/maki": "^8.2.0",
"@rapideditor/temaki": "^5.13.0",
"@tailwindcss/postcss": "^4.2.4",
"@testing-library/react": "^16.3.2",
"@types/node": "^25.6.0",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"esbuild": "^0.28.0",
"jsdom": "^29.1.1",
"tailwindcss": "^4.2.4",
"typescript": "^5.9.3"
}
Expand Down
13 changes: 11 additions & 2 deletions apps/web/src/components/map/layers/DataSourceLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import {
import type { DataSourceAttribution } from "@openmapx/integration-framework";
import { dataSourceToAttribution } from "@openmapx/integration-framework";
import { useIntegrationRegistry } from "@openmapx/integration-framework/react";
import { isI18nToken, type Translatable } from "@openmapx/integration-framework/strings";
import type { Attribution } from "@openmapx/mobility-core/attribution";
import type maplibregl from "maplibre-gl";
import type { GeoJSONSource, Map as MaplibreMap, MapMouseEvent } from "maplibre-gl";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useDataSourceI18nResolver } from "@/components/panels/place/useDataSourceI18nResolver";
import { usePinMarker } from "@/hooks/usePinMarker";
import { translateDataSourceSummary } from "@/lib/dataSourceSummaryI18n";
import { INTERACTIVE_LAYER_IDS } from "@/lib/interactiveLayers";
Expand Down Expand Up @@ -63,7 +65,7 @@ function mapContextOutlineLayerId(dsId: string) {

function buildGeoJson(
results: DataSourceResult[],
translateSummary: (summary: string | undefined) => string | undefined,
translateSummary: (summary: Translatable | undefined) => string | undefined,
imageId?: string,
) {
return {
Expand Down Expand Up @@ -218,6 +220,7 @@ export function DataSourceLayer() {
}, [activeSource, sourcesData]);

const registry = useIntegrationRegistry();
const resolveToken = useDataSourceI18nResolver(activeSource ?? undefined);

// Separate server-side filters (sent to the API) from client-side filters
// (applied locally on the result set). Uses the `clientSide` flag from
Expand Down Expand Up @@ -419,7 +422,12 @@ export function DataSourceLayer() {
const imageId = useIconMarkers ? `ds-marker-${activeSource}` : undefined;
const geojson = buildGeoJson(
filteredResults,
(summary) => translateDataSourceSummary(summary, t),
(summary) => {
if (summary === undefined) return undefined;
if (isI18nToken(summary)) return resolveToken(summary);
if (typeof summary === "number") return String(summary);
return translateDataSourceSummary(summary, t);
},
imageId,
);

Expand Down Expand Up @@ -645,6 +653,7 @@ export function DataSourceLayer() {
mapRef,
mapContext,
t,
resolveToken,
]);

const { setSelectedPlace } = usePlaceStore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ import {
usePlaceStore,
useSidebarStore,
} from "@openmapx/core";
import { isI18nToken } from "@openmapx/integration-framework/strings";
import { useTranslations } from "next-intl";
import { useCallback, useMemo, useState } from "react";
import { useDataSourceI18nResolver } from "@/components/panels/place/useDataSourceI18nResolver";
import { translateDataSourceLabel, translateDataSourceSummary } from "@/lib/dataSourceSummaryI18n";
import { TEAL } from "@/lib/theme";
import { BrandMark } from "../shared/BrandMark";
Expand Down Expand Up @@ -199,6 +201,7 @@ export function DataSourceFilterContent() {
const t = useTranslations("dataSources");
const tc = useTranslations("common");
const activeSource = useDataSourceStore((s) => s.activeSource);
const resolveToken = useDataSourceI18nResolver(activeSource ?? undefined);
const filters = useDataSourceStore((s) => s.filters);
const searchBbox = useDataSourceStore((s) => s.searchBbox);
const viewportZoom = useDataSourceStore((s) => s.viewportZoom);
Expand Down Expand Up @@ -537,7 +540,12 @@ export function DataSourceFilterContent() {
{result.summary && (
<Typography variant="caption" color="text.secondary">
<FormattedSummary
text={translateDataSourceSummary(result.summary, t) ?? result.summary}
text={
isI18nToken(result.summary)
? resolveToken(result.summary)
: (translateDataSourceSummary(result.summary, t) ??
result.summary)
}
/>
</Typography>
)}
Expand Down
Loading
Loading