Add one change & crime

This commit is contained in:
Andras Schmelczer 2026-06-14 14:51:29 +01:00
parent 8e4c56bb0d
commit 54fbcb1ea6
17 changed files with 306 additions and 154 deletions

View file

@ -92,6 +92,7 @@ interface FiltersProps {
onTravelTimeDragEnd: (index: number) => void;
onTravelTimeToggleBest: (index: number) => void;
onTravelTimeToggleNoChange: (index: number) => void;
onTravelTimeToggleOneChange: (index: number) => void;
onTravelTimeToggleNoBuses: (index: number) => void;
aiFilterLoading: boolean;
aiFilterError: string | null;
@ -139,6 +140,7 @@ export default memo(function Filters({
onTravelTimeDragEnd,
onTravelTimeToggleBest,
onTravelTimeToggleNoChange,
onTravelTimeToggleOneChange,
onTravelTimeToggleNoBuses,
aiFilterLoading,
aiFilterError,
@ -667,6 +669,7 @@ export default memo(function Filters({
onTravelTimeDragEnd={onTravelTimeDragEnd}
onTravelTimeToggleBest={onTravelTimeToggleBest}
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
/>

View file

@ -181,6 +181,7 @@ export default function MapPage({
handleTimeRangeChange,
handleToggleBest,
handleToggleNoChange,
handleToggleOneChange,
handleToggleNoBuses,
} = useTravelTime(initialTravelTime);
@ -248,6 +249,7 @@ export default function MapPage({
representable.map(({ tt, parsed }) => ({
mode: parsed.mode,
noChange: parsed.noChange,
oneChange: parsed.oneChange,
noBuses: parsed.noBuses,
slug: tt.slug,
label: tt.label,
@ -800,6 +802,7 @@ export default function MapPage({
onTravelTimeDragEnd={handleTravelTimeDragEnd}
onTravelTimeToggleBest={handleToggleBest}
onTravelTimeToggleNoChange={handleToggleNoChange}
onTravelTimeToggleOneChange={handleToggleOneChange}
onTravelTimeToggleNoBuses={handleToggleNoBuses}
aiFilterLoading={aiFilterLoading}
aiFilterError={aiFilterError}
@ -853,6 +856,7 @@ export default function MapPage({
handleToggleBest,
handleToggleNoBuses,
handleToggleNoChange,
handleToggleOneChange,
handleTogglePin,
handleTravelTimeDragEnd,
handleTravelTimeRemoveEntry,

View file

@ -27,6 +27,7 @@ interface TravelTimeCardProps {
timeRange: [number, number] | null;
useBest: boolean;
noChange: boolean;
oneChange: boolean;
noBuses: boolean;
isPinned: boolean;
isActive: boolean;
@ -39,6 +40,7 @@ interface TravelTimeCardProps {
onDragEnd: () => void;
onToggleBest: () => void;
onToggleNoChange: () => void;
onToggleOneChange: () => void;
onToggleNoBuses: () => void;
onRemove: () => void;
filterImpact?: number;
@ -52,6 +54,7 @@ export function TravelTimeCard({
timeRange,
useBest,
noChange,
oneChange,
noBuses,
isPinned,
isActive,
@ -64,6 +67,7 @@ export function TravelTimeCard({
onDragEnd,
onToggleBest,
onToggleNoChange,
onToggleOneChange,
onToggleNoBuses,
onRemove,
filterImpact,
@ -75,6 +79,7 @@ export function TravelTimeCard({
const [showInfo, setShowInfo] = useState(false);
const [showBestInfo, setShowBestInfo] = useState(false);
const [showNoChangeInfo, setShowNoChangeInfo] = useState(false);
const [showOneChangeInfo, setShowOneChangeInfo] = useState(false);
const [showNoBusesInfo, setShowNoBusesInfo] = useState(false);
const handleDestinationSelect = useCallback(
@ -169,6 +174,20 @@ export function TravelTimeCard({
<InfoIcon className="w-3 h-3" />
</IconButton>
</div>
<div className="flex items-center gap-0.5">
<PillToggle
label={t('travel.oneChange')}
active={oneChange}
onClick={onToggleOneChange}
size="xs"
/>
<IconButton
onClick={() => setShowOneChangeInfo(true)}
title={t('travel.oneChangeTitle')}
>
<InfoIcon className="w-3 h-3" />
</IconButton>
</div>
<div className="flex items-center gap-0.5">
<PillToggle
label={t('travel.noBuses')}
@ -201,6 +220,14 @@ export function TravelTimeCard({
</InfoPopup>
)}
{showOneChangeInfo && (
<InfoPopup title={t('travel.oneChangeTitle')} onClose={() => setShowOneChangeInfo(false)}>
<p className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed">
<Trans i18nKey="travel.oneChangeDesc" components={{ strong: <strong /> }} />
</p>
</InfoPopup>
)}
{showNoBusesInfo && (
<InfoPopup title={t('travel.noBusesTitle')} onClose={() => setShowNoBusesInfo(false)}>
<p className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed">

View file

@ -59,6 +59,7 @@ interface ActiveFilterListProps {
onTravelTimeDragEnd: (index: number) => void;
onTravelTimeToggleBest: (index: number) => void;
onTravelTimeToggleNoChange: (index: number) => void;
onTravelTimeToggleOneChange: (index: number) => void;
onTravelTimeToggleNoBuses: (index: number) => void;
}
@ -88,6 +89,7 @@ export function ActiveFilterList({
onTravelTimeDragEnd,
onTravelTimeToggleBest,
onTravelTimeToggleNoChange,
onTravelTimeToggleOneChange,
onTravelTimeToggleNoBuses,
}: ActiveFilterListProps) {
const travelCards = (
@ -105,6 +107,7 @@ export function ActiveFilterList({
onTravelTimeDragEnd={onTravelTimeDragEnd}
onTravelTimeToggleBest={onTravelTimeToggleBest}
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
onDragStart={onDragStart}
onDragChange={onDragChange}

View file

@ -57,6 +57,7 @@ interface ActiveFiltersPanelProps {
onTravelTimeDragEnd: (index: number) => void;
onTravelTimeToggleBest: (index: number) => void;
onTravelTimeToggleNoChange: (index: number) => void;
onTravelTimeToggleOneChange: (index: number) => void;
onTravelTimeToggleNoBuses: (index: number) => void;
}
@ -102,6 +103,7 @@ export function ActiveFiltersPanel({
onTravelTimeDragEnd,
onTravelTimeToggleBest,
onTravelTimeToggleNoChange,
onTravelTimeToggleOneChange,
onTravelTimeToggleNoBuses,
}: ActiveFiltersPanelProps) {
const { t } = useTranslation();
@ -206,6 +208,7 @@ export function ActiveFiltersPanel({
onTravelTimeDragEnd={onTravelTimeDragEnd}
onTravelTimeToggleBest={onTravelTimeToggleBest}
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
/>
</div>

View file

@ -21,6 +21,7 @@ interface TravelTimeFilterCardsProps {
onTravelTimeDragEnd: (index: number) => void;
onTravelTimeToggleBest: (index: number) => void;
onTravelTimeToggleNoChange: (index: number) => void;
onTravelTimeToggleOneChange: (index: number) => void;
onTravelTimeToggleNoBuses: (index: number) => void;
onDragStart: (name: string, initialValue?: [number, number]) => void;
onDragChange: (value: [number, number]) => void;
@ -40,6 +41,7 @@ export function TravelTimeFilterCards({
onTravelTimeDragEnd,
onTravelTimeToggleBest,
onTravelTimeToggleNoChange,
onTravelTimeToggleOneChange,
onTravelTimeToggleNoBuses,
onDragStart,
onDragChange,
@ -57,6 +59,7 @@ export function TravelTimeFilterCards({
timeRange={entry.timeRange}
useBest={entry.useBest}
noChange={entry.noChange ?? false}
oneChange={entry.oneChange ?? false}
noBuses={entry.noBuses ?? false}
isPinned={pinnedFeature === fieldKey}
isActive={activeFeature === fieldKey}
@ -71,6 +74,7 @@ export function TravelTimeFilterCards({
onDragEnd={() => onTravelTimeDragEnd(index)}
onToggleBest={() => onTravelTimeToggleBest(index)}
onToggleNoChange={() => onTravelTimeToggleNoChange(index)}
onToggleOneChange={() => onTravelTimeToggleOneChange(index)}
onToggleNoBuses={() => onTravelTimeToggleNoBuses(index)}
onRemove={() => onTravelTimeRemoveEntry(index)}
filterImpact={filterImpacts?.[fieldKey]}

View file

@ -150,6 +150,12 @@ export function useExportController({
const params = new URLSearchParams();
if (isListMode) {
params.set('postcodes', postcodeList.join(','));
// Pass the active filters so the export keeps its two sheets — "Selected"
// (the filtered feature columns) and "All Data" — even in list mode. The
// server uses them only to pick columns; the supplied postcodes still
// drive which rows appear.
const filterStr = buildFilterString(filters, features);
if (filterStr) params.set('filters', filterStr);
if (shareCode) params.set('share', shareCode);
} else {
const { south, west, north, east } = bounds!;

View file

@ -34,6 +34,7 @@ describe('useTravelTime', () => {
timeRange: null,
useBest: false,
noChange: false,
oneChange: false,
noBuses: false,
},
]);
@ -143,6 +144,27 @@ describe('useTravelTime', () => {
act(() => result.current.handleToggleNoChange(0));
expect(result.current.entries[0]).toMatchObject({ noChange: false, noBuses: true });
});
it('keeps noChange and oneChange mutually exclusive', () => {
const { result } = renderHook(() => useTravelTime());
act(() => result.current.handleAddEntry('transit'));
act(() => result.current.handleSetDestination(0, 'bank', 'Bank'));
act(() => result.current.handleToggleNoChange(0));
expect(result.current.entries[0]).toMatchObject({ noChange: true, oneChange: false });
// Enabling oneChange clears noChange...
act(() => result.current.handleToggleOneChange(0));
expect(result.current.entries[0]).toMatchObject({ noChange: false, oneChange: true });
// ...and vice versa.
act(() => result.current.handleToggleNoChange(0));
expect(result.current.entries[0]).toMatchObject({ noChange: true, oneChange: false });
// Both can be off (no restriction): toggling the active one off leaves neither set.
act(() => result.current.handleToggleNoChange(0));
expect(result.current.entries[0]).toMatchObject({ noChange: false, oneChange: false });
});
});
describe('resolveTransitVariant', () => {
@ -163,10 +185,20 @@ describe('resolveTransitVariant', () => {
it('maps transit toggle combinations to the right variant string', () => {
expect(resolveTransitVariant(base)).toBe('transit');
expect(resolveTransitVariant({ ...base, noChange: true })).toBe('transit-no-change');
expect(resolveTransitVariant({ ...base, oneChange: true })).toBe('transit-one-change');
expect(resolveTransitVariant({ ...base, noBuses: true })).toBe('transit-no-bus');
expect(resolveTransitVariant({ ...base, noChange: true, noBuses: true })).toBe(
'transit-no-change-no-bus'
);
expect(resolveTransitVariant({ ...base, oneChange: true, noBuses: true })).toBe(
'transit-one-change-no-bus'
);
});
it('lets noChange win if both change flags are somehow set', () => {
expect(resolveTransitVariant({ ...base, noChange: true, oneChange: true })).toBe(
'transit-no-change'
);
});
it('treats undefined flags as false', () => {
@ -177,47 +209,69 @@ describe('resolveTransitVariant', () => {
});
describe('parseServerMode', () => {
it('round-trips the four toggle-reachable variants', () => {
it('round-trips the six toggle-reachable transit variants', () => {
expect(parseServerMode('transit')).toEqual({
mode: 'transit',
noChange: false,
oneChange: false,
noBuses: false,
});
expect(parseServerMode('transit-no-bus')).toEqual({
mode: 'transit',
noChange: false,
oneChange: false,
noBuses: true,
});
expect(parseServerMode('transit-no-change')).toEqual({
mode: 'transit',
noChange: true,
oneChange: false,
noBuses: false,
});
expect(parseServerMode('transit-no-change-no-bus')).toEqual({
mode: 'transit',
noChange: true,
oneChange: false,
noBuses: true,
});
expect(parseServerMode('transit-one-change')).toEqual({
mode: 'transit',
noChange: false,
oneChange: true,
noBuses: false,
});
expect(parseServerMode('transit-one-change-no-bus')).toEqual({
mode: 'transit',
noChange: false,
oneChange: true,
noBuses: true,
});
});
it('parses non-transit base modes', () => {
expect(parseServerMode('car')).toEqual({ mode: 'car', noChange: false, noBuses: false });
expect(parseServerMode('car')).toEqual({
mode: 'car',
noChange: false,
oneChange: false,
noBuses: false,
});
expect(parseServerMode('bicycle')).toEqual({
mode: 'bicycle',
noChange: false,
oneChange: false,
noBuses: false,
});
expect(parseServerMode('walking')).toEqual({
mode: 'walking',
noChange: false,
oneChange: false,
noBuses: false,
});
});
it('returns null for variants the UI cannot represent (no silent broadening)', () => {
expect(parseServerMode('transit-one-change')).toBeNull();
expect(parseServerMode('transit-one-change-no-bus')).toBeNull();
expect(parseServerMode('unknown-mode')).toBeNull();
expect(parseServerMode('transit-two-change')).toBeNull();
});
it('travelFieldKey uses the resolved variant', () => {

View file

@ -66,6 +66,9 @@ export interface TravelTimeEntry {
useBest: boolean;
/** Restrict transit to walk-transit-walk (0 changes). Optional; defaults to false. */
noChange?: boolean;
/** Restrict transit to at most one change. Optional; defaults to false.
* Mutually exclusive with noChange (the UI never sets both). */
oneChange?: boolean;
/** Drop buses from the allowed transit modes. Optional; defaults to false. */
noBuses?: boolean;
}
@ -77,42 +80,44 @@ export interface TravelTimeEntry {
*
* For non-transit modes the entry.mode passes through unchanged.
*
* Note: the transit-one-change* variants exist server-side but are not reachable
* from the UI toggles (only no-change + no-buses are exposed). They're available
* via direct API access for callers that want them.
* noChange and oneChange are mutually exclusive (the UI never sets both); if
* they ever were, noChange wins here.
*/
export function resolveTransitVariant(entry: TravelTimeEntry): string {
if (entry.mode !== 'transit') return entry.mode;
const nc = entry.noChange ?? false;
const oc = entry.oneChange ?? false;
const nb = entry.noBuses ?? false;
if (nc && nb) return 'transit-no-change-no-bus';
if (nc) return 'transit-no-change';
if (nb) return 'transit-no-bus';
return 'transit';
if (nc) return nb ? 'transit-no-change-no-bus' : 'transit-no-change';
if (oc) return nb ? 'transit-one-change-no-bus' : 'transit-one-change';
return nb ? 'transit-no-bus' : 'transit';
}
/**
* Parse a server-side mode string (incl. transit variants) back into a base
* TransportMode + UI toggle booleans. Returns null for mode strings the UI
* cannot represent (currently: transit-one-change, transit-one-change-no-bus).
* Callers should skip entries that parse to null rather than silently
* normalising to a different variant.
* cannot represent. Callers should skip entries that parse to null rather than
* silently normalising to a different variant.
*/
export function parseServerMode(
modeStr: string
): { mode: TransportMode; noChange: boolean; noBuses: boolean } | null {
): { mode: TransportMode; noChange: boolean; oneChange: boolean; noBuses: boolean } | null {
if (modeStr === 'car' || modeStr === 'bicycle' || modeStr === 'walking') {
return { mode: modeStr, noChange: false, noBuses: false };
return { mode: modeStr, noChange: false, oneChange: false, noBuses: false };
}
switch (modeStr) {
case 'transit':
return { mode: 'transit', noChange: false, noBuses: false };
return { mode: 'transit', noChange: false, oneChange: false, noBuses: false };
case 'transit-no-bus':
return { mode: 'transit', noChange: false, noBuses: true };
return { mode: 'transit', noChange: false, oneChange: false, noBuses: true };
case 'transit-no-change':
return { mode: 'transit', noChange: true, noBuses: false };
return { mode: 'transit', noChange: true, oneChange: false, noBuses: false };
case 'transit-no-change-no-bus':
return { mode: 'transit', noChange: true, noBuses: true };
return { mode: 'transit', noChange: true, oneChange: false, noBuses: true };
case 'transit-one-change':
return { mode: 'transit', noChange: false, oneChange: true, noBuses: false };
case 'transit-one-change-no-bus':
return { mode: 'transit', noChange: false, oneChange: true, noBuses: true };
default:
return null;
}
@ -145,6 +150,7 @@ export function useTravelTime(initial?: TravelTimeInitial) {
timeRange: null,
useBest: false,
noChange: false,
oneChange: false,
noBuses: false,
},
]);
@ -182,11 +188,27 @@ export function useTravelTime(initial?: TravelTimeInitial) {
);
}, []);
// noChange and oneChange are mutually exclusive: enabling one clears the
// other so the resolved server variant is never ambiguous.
const handleToggleNoChange = useCallback((index: number) => {
setEntries((prev) =>
dedupeTravelTimeEntries(
prev.map((entry, i) =>
i === index ? { ...entry, noChange: !(entry.noChange ?? false) } : entry
i === index
? { ...entry, noChange: !(entry.noChange ?? false), oneChange: false }
: entry
)
)
);
}, []);
const handleToggleOneChange = useCallback((index: number) => {
setEntries((prev) =>
dedupeTravelTimeEntries(
prev.map((entry, i) =>
i === index
? { ...entry, oneChange: !(entry.oneChange ?? false), noChange: false }
: entry
)
)
);
@ -219,6 +241,7 @@ export function useTravelTime(initial?: TravelTimeInitial) {
handleTimeRangeChange,
handleToggleBest,
handleToggleNoChange,
handleToggleOneChange,
handleToggleNoBuses,
};
}

View file

@ -96,19 +96,19 @@ export const details: Record<string, Record<string, string>> = {
'Median age':
"Provient du Census 2021 (TS007A). Âge médian des résidents habituels dans le LSOA, calculé par interpolation linéaire à partir des effectifs par tranche d'âge de cinq ans. Les zones à population plus jeune ont tendance à être urbaines, universitaires ou à accueillir davantage de familles ; les médianes plus élevées sont typiques des zones rurales et côtières.",
'% White':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Blanc (anglais, gallois, écossais, nord-irlandais, britannique, irlandais, Gitan ou Voyageur irlandais, Rom, ou tout autre origine blanche).",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme Blanc (anglais, gallois, écossais, nord-irlandais, britannique, irlandais, Gitan ou Voyageur irlandais, Rom, ou tout autre origine blanche).",
'% South Asian':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Indien, Pakistanais, Bangladais ou toute autre origine asiatique.",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme Indien, Pakistanais ou Bangladais.",
'% Black':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Noir, Noir britannique, Caribéen ou Africain.",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme Noir, Noir britannique, Caribéen ou Africain.",
'% East Asian':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Chinois.",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme Chinois.",
'% SE Asian':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme appartenant à une autre origine est/sud-est asiatique (par ex. Philippin, Vietnamien ou Thaïlandais).",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme appartenant à une autre origine est/sud-est asiatique (par ex. Philippin, Vietnamien ou Thaïlandais).",
'% Mixed':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Mixte ou appartenant à plusieurs groupes ethniques (Blanc et Noir caribéen, Blanc et Noir africain, Blanc et Asiatique, ou tout autre fond mixte ou multiple).",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme Mixte ou appartenant à plusieurs groupes ethniques (Blanc et Noir caribéen, Blanc et Noir africain, Blanc et Asiatique, ou tout autre fond mixte ou multiple).",
'% Other':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme appartenant à un autre groupe ethnique (Arabe ou tout autre groupe ethnique non couvert par les catégories principales).",
"Provient du Census 2021. Pourcentage de la population du quartier (LSOA) s'identifiant comme appartenant à un autre groupe ethnique (Arabe ou tout autre groupe ethnique non couvert par les catégories principales).",
'Voter turnout (%)':
"La proportion de l'électorat inscrit qui a voté de manière valide lors des élections générales britanniques de juillet 2024. Calculée comme le nombre de votes valides divisé par la taille de l'électorat. Une participation plus élevée est généralement corrélée avec des zones plus aisées et des scrutins plus serrés.",
'% Labour':
@ -236,19 +236,19 @@ export const details: Record<string, Record<string, string>> = {
'Median age':
'Aus dem Census 2021 (TS007A). Medianalter der ortsansässigen Bevölkerung im LSOA, berechnet durch lineare Interpolation aus Fünfjahres-Altersband-Zählungen. Gebiete mit jüngerer Bevölkerung sind tendenziell städtisch, Universitätsstädte oder haben mehr Familien; höhere Mediane sind typisch für ländliche und Küstengebiete.',
'% White':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Weiß identifiziert (Englisch, Walisisch, Schottisch, Nordirisch, Britisch, Irisch, Sinti und Roma, Roma oder sonstiger weißer Hintergrund).',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als Weiß identifiziert (Englisch, Walisisch, Schottisch, Nordirisch, Britisch, Irisch, Sinti und Roma, Roma oder sonstiger weißer Hintergrund).',
'% South Asian':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Indisch, Pakistanisch, Bangladeschisch oder mit sonstigem asiatischen Hintergrund identifiziert.',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als Indisch, Pakistanisch oder Bangladeschisch identifiziert.',
'% Black':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Schwarz, Schwarz-Britisch, Karibisch oder Afrikanisch identifiziert.',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als Schwarz, Schwarz-Britisch, Karibisch oder Afrikanisch identifiziert.',
'% East Asian':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Chinesisch identifiziert.',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als Chinesisch identifiziert.',
'% SE Asian':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich mit einer anderen (nicht chinesischen) ost-/südostasiatischen Herkunft identifiziert, z. B. philippinisch, vietnamesisch oder thailändisch.',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich mit einer anderen (nicht chinesischen) ost-/südostasiatischen Herkunft identifiziert, z. B. philippinisch, vietnamesisch oder thailändisch.',
'% Mixed':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als gemischt oder mit mehreren ethnischen Zugehörigkeiten identifiziert (Weiß und Schwarzkaribisch, Weiß und Schwarzafrikanisch, Weiß und Asiatisch oder sonstiger gemischter Hintergrund).',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als gemischt oder mit mehreren ethnischen Zugehörigkeiten identifiziert (Weiß und Schwarzkaribisch, Weiß und Schwarzafrikanisch, Weiß und Asiatisch oder sonstiger gemischter Hintergrund).',
'% Other':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als einer anderen ethnischen Gruppe zugehörig identifiziert (Arabisch oder eine andere ethnische Gruppe, die nicht von den Hauptkategorien abgedeckt wird).',
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Nachbarschaft (LSOA), die sich als einer anderen ethnischen Gruppe zugehörig identifiziert (Arabisch oder eine andere ethnische Gruppe, die nicht von den Hauptkategorien abgedeckt wird).',
'Voter turnout (%)':
'Der Anteil der registrierten Wahlberechtigten, die bei der britischen Parlamentswahl im Juli 2024 eine gültige Stimme abgegeben haben. Berechnet als gültige Stimmen geteilt durch die Größe der Wählerschaft. Eine höhere Wahlbeteiligung korreliert im Allgemeinen mit wohlhabenderen Gebieten und knapperen Ergebnissen.',
'% Labour':
@ -374,17 +374,18 @@ export const details: Record<string, Record<string, string>> = {
'Median age':
'来自2021年CensusTS007A。通过对五岁年龄段人口数进行线性插值计算得出的LSOA常住居民年龄中位数。年轻人口集中的地区往往是城市、大学城或家庭聚居地年龄中位数较高的地区多见于农村和沿海地区。',
'% White':
'来自2021年Census。地方政府人口中认同为白人(英格兰人、威尔士人、苏格兰人、北爱尔兰人、英国人、爱尔兰人、吉普赛人或爱尔兰旅行者、罗姆人或其他白人背景)的百分比。',
'来自2021年Census。本地社区LSOA人口中认同为白人(英格兰人、威尔士人、苏格兰人、北爱尔兰人、英国人、爱尔兰人、吉普赛人或爱尔兰旅行者、罗姆人或其他白人背景)的百分比。',
'% South Asian':
'来自2021年Census。地方政府人口中认同为印度人、巴基斯坦人、孟加拉国人或其他亚洲背景的百分比。',
'% Black': '来自2021年Census。地方政府人口中认同为黑人、英国黑人、加勒比人或非洲人的百分比。',
'% East Asian': '来自2021年Census。地方政府人口中认同为华人的百分比。',
'来自2021年Census。本地社区LSOA人口中认同为印度人、巴基斯坦人或孟加拉国人的百分比。',
'% Black':
'来自2021年Census。本地社区LSOA人口中认同为黑人、英国黑人、加勒比人或非洲人的百分比。',
'% East Asian': '来自2021年Census。本地社区LSOA人口中认同为华人的百分比。',
'% SE Asian':
'来自2021年Census。地方政府人口中认同为其他(非华人)东亚/东南亚裔(如菲律宾裔、越南裔或泰裔)的百分比。',
'来自2021年Census。本地社区LSOA人口中认同为其他(非华人)东亚/东南亚裔(如菲律宾裔、越南裔或泰裔)的百分比。',
'% Mixed':
'来自2021年 Census。地方政府人口中认同为混血或多种族群体(白人与黑人加勒比裔、白人与黑人非洲裔、白人与亚洲裔,或其他混血或多种族背景)的百分比。',
'来自2021年 Census。本地社区LSOA人口中认同为混血或多种族群体(白人与黑人加勒比裔、白人与黑人非洲裔、白人与亚洲裔,或其他混血或多种族背景)的百分比。',
'% Other':
'来自2021年Census。地方政府人口中认同为其他族裔群体(阿拉伯人或其他未被主要类别涵盖的族裔)的百分比。',
'来自2021年Census。本地社区LSOA人口中认同为其他族裔群体(阿拉伯人或其他未被主要类别涵盖的族裔)的百分比。',
'Voter turnout (%)':
'2024年7月英国大选中投出有效选票的登记选民比例。计算方式为有效票数除以选民总数。较高的投票率通常与较富裕地区和竞争更激烈的选举相关。',
'% Labour':
@ -508,19 +509,19 @@ export const details: Record<string, Record<string, string>> = {
'Median age':
'Census 2021 (TS007A) से. LSOA में सामान्य निवासियों की मध्य आयु, पांच-वर्षीय आयु समूहों की गणना से रैखिक इंटरपोलेशन द्वारा निकाली गई. युवा आबादी वाले क्षेत्र आमतौर पर शहरी, विश्वविद्यालय नगर या अधिक परिवारों वाले होते हैं; अधिक मध्य आयु वाले क्षेत्र आमतौर पर ग्रामीण और तटीय इलाकों में मिलते हैं.',
'% White':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को श्वेत (अंग्रेज़, वेल्श, स्कॉटिश, उत्तरी आयरिश, ब्रिटिश, आयरिश, जिप्सी या आयरिश ट्रैवलर, रोमा या किसी अन्य श्वेत पृष्ठभूमि) के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को श्वेत (अंग्रेज़, वेल्श, स्कॉटिश, उत्तरी आयरिश, ब्रिटिश, आयरिश, जिप्सी या आयरिश ट्रैवलर, रोमा या किसी अन्य श्वेत पृष्ठभूमि) के रूप में पहचानता है.',
'% South Asian':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को भारतीय, पाकिस्तानी, बांग्लादेशी या किसी अन्य एशियाई पृष्ठभूमि के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को भारतीय, पाकिस्तानी या बांग्लादेशी के रूप में पहचानता है.',
'% Black':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को अश्वेत, अश्वेत ब्रिटिश, कैरिबियाई या अफ्रीकी के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को अश्वेत, अश्वेत ब्रिटिश, कैरिबियाई या अफ्रीकी के रूप में पहचानता है.',
'% East Asian':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को चीनी के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को चीनी के रूप में पहचानता है.',
'% SE Asian':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को अन्य (गैर-चीनी) पूर्वी/दक्षिण-पूर्वी एशियाई (जैसे फिलिपिनो, वियतनामी या थाई) के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को अन्य (गैर-चीनी) पूर्वी/दक्षिण-पूर्वी एशियाई (जैसे फिलिपिनो, वियतनामी या थाई) के रूप में पहचानता है.',
'% Mixed':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को मिश्रित या कई जातीय समूहों (श्वेत और अश्वेत कैरिबियाई, श्वेत और अश्वेत अफ्रीकी, श्वेत और एशियाई या अन्य मिश्रित/बहुजातीय पृष्ठभूमि) के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को मिश्रित या कई जातीय समूहों (श्वेत और अश्वेत कैरिबियाई, श्वेत और अश्वेत अफ्रीकी, श्वेत और एशियाई या अन्य मिश्रित/बहुजातीय पृष्ठभूमि) के रूप में पहचानता है.',
'% Other':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को किसी अन्य जातीय समूह (अरब या मुख्य श्रेणियों से बाहर किसी अन्य जातीय समूह) के रूप में पहचानता है.',
'Census 2021 से. स्थानीय पड़ोस (LSOA) की आबादी का प्रतिशत जो खुद को किसी अन्य जातीय समूह (अरब या मुख्य श्रेणियों से बाहर किसी अन्य जातीय समूह) के रूप में पहचानता है.',
'Voter turnout (%)':
'जुलाई 2024 के ब्रिटेन के आम चुनाव में वैध मत देने वाले पंजीकृत मतदाताओं का अनुपात. इसकी गणना वैध मतों को मतदाता सूची के आकार से भाग देकर की जाती है. अधिक मतदान आमतौर पर संपन्न क्षेत्रों और कड़ी चुनावी प्रतिस्पर्धा से जुड़ा होता है.',
'% Labour':
@ -648,19 +649,19 @@ export const details: Record<string, Record<string, string>> = {
'Median age':
'A 2021-es Census alapján (TS007A). Az LSOA szokásos lakóinak medián életkora, ötéves korcsoport-számlálásokból lineáris interpolációval számítva. A fiatalabb népességű területek jellemzően városiak, egyetemi városok vagy több családot vonzanak; az idősebb medián értékek jellemzően vidéki és tengerparti területekre jellemzők.',
'% White':
'A 2021-es Census alapján. A helyi hatóság területén fehérként (angol, walesi, skót, észak-ír, brit, ír, cigány vagy ír vándor, roma, vagy bármely más fehér háttér) azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) fehérként (angol, walesi, skót, észak-ír, brit, ír, cigány vagy ír vándor, roma, vagy bármely más fehér háttér) azonosított népesség százaléka.',
'% South Asian':
'A 2021-es Census alapján. A helyi hatóság területén indiai, pakisztáni, bangladesi vagy bármely más ázsiai háttérként azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) indiai, pakisztáni vagy bangladesi háttérként azonosított népesség százaléka.',
'% Black':
'A 2021-es Census alapján. A helyi hatóság területén fekete, brit fekete, karibi vagy afrikai háttérként azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) fekete, brit fekete, karibi vagy afrikai háttérként azonosított népesség százaléka.',
'% East Asian':
'A 2021-es Census alapján. A helyi hatóság területén kínaiként azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) kínaiként azonosított népesség százaléka.',
'% SE Asian':
'A 2021-es Census alapján. A helyi hatóság területén más (nem kínai) kelet-/délkelet-ázsiai származásúként (pl. filippínó, vietnámi vagy thai) azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) más (nem kínai) kelet-/délkelet-ázsiai származásúként (pl. filippínó, vietnámi vagy thai) azonosított népesség százaléka.',
'% Mixed':
'A 2021-es Census alapján. A helyi hatóság területén vegyes vagy többes etnikai csoportként (fehér és fekete karibi, fehér és fekete afrikai, fehér és ázsiai, vagy bármely más vegyes vagy többes háttér) azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) vegyes vagy többes etnikai csoportként (fehér és fekete karibi, fehér és fekete afrikai, fehér és ázsiai, vagy bármely más vegyes vagy többes háttér) azonosított népesség százaléka.',
'% Other':
'A 2021-es Census alapján. A helyi hatóság területén egyéb etnikai csoportként (arab vagy bármely más, a főkategóriák által nem lefedett etnikai csoport) azonosított népesség százaléka.',
'A 2021-es Census alapján. A helyi környéken (LSOA) egyéb etnikai csoportként (arab vagy bármely más, a főkategóriák által nem lefedett etnikai csoport) azonosított népesség százaléka.',
'Voter turnout (%)':
'A regisztrált szavazók azon aránya, akik érvényes szavazatot adtak le a 2024. júliusi brit parlamenti választáson. Az érvényes szavazatok száma osztva a választói névjegyzékben szereplők számával. A magasabb részvétel általában a tehetősebb területekkel és a szorosabb versenyekkel korrelál.',
'% Labour':

View file

@ -746,6 +746,10 @@ const de: Translations = {
noChangeTitle: 'Nur direkte Verbindungen',
noChangeDesc:
'Beschränkt auf Fahrten <strong>ohne Umstiege</strong>: hinlaufen, ein Verkehrsmittel nehmen, am Ziel weiterlaufen.',
oneChange: 'Ein Umstieg',
oneChangeTitle: 'Fahrten mit einem Umstieg',
oneChangeDesc:
'Erlaubt Fahrten mit <strong>höchstens einem Umstieg</strong>: ein einzelner Wechsel zwischen zwei Verkehrsmitteln ist erlaubt, mehr nicht. Ein Mittelweg zwischen direkten und uneingeschränkten Fahrten.',
noBuses: 'Ohne Bus',
noBusesTitle: 'Busse ausschließen',
noBusesDesc:
@ -1546,22 +1550,22 @@ const de: Translations = {
'Air Quality and Road Safety Score': 'Wert für Luftqualität und Verkehrssicherheit',
// ─ Feature names (Crime) ─
'Serious crime (avg/yr)': 'Schwere Straftaten (Ø/Jahr)',
'Minor crime (avg/yr)': 'Leichte Straftaten (Ø/Jahr)',
'Violence and sexual offences (avg/yr)': 'Gewalt- und Sexualdelikte (Ø/Jahr)',
'Burglary (avg/yr)': 'Einbrüche (Ø/Jahr)',
'Robbery (avg/yr)': 'Raubüberfälle (Ø/Jahr)',
'Vehicle crime (avg/yr)': 'Fahrzeugkriminalität (Ø/Jahr)',
'Anti-social behaviour (avg/yr)': 'Antisoziales Verhalten (Ø/Jahr)',
'Criminal damage and arson (avg/yr)': 'Sachbeschädigung und Brandstiftung (Ø/Jahr)',
'Other theft (avg/yr)': 'Sonstiger Diebstahl (Ø/Jahr)',
'Theft from the person (avg/yr)': 'Diebstahl von der Person (Ø/Jahr)',
'Shoplifting (avg/yr)': 'Ladendiebstahl (Ø/Jahr)',
'Bicycle theft (avg/yr)': 'Fahrraddiebstahl (Ø/Jahr)',
'Drugs (avg/yr)': 'Drogendelikte (Ø/Jahr)',
'Possession of weapons (avg/yr)': 'Waffenbesitz (Ø/Jahr)',
'Public order (avg/yr)': 'Störung der öffentlichen Ordnung (Ø/Jahr)',
'Other crime (avg/yr)': 'Sonstige Straftaten (Ø/Jahr)',
'Serious crime (avg/yr)': 'Schwere Straftaten (Dichte)',
'Minor crime (avg/yr)': 'Leichte Straftaten (Dichte)',
'Violence and sexual offences (avg/yr)': 'Gewalt- und Sexualdelikte (Dichte)',
'Burglary (avg/yr)': 'Einbrüche (Dichte)',
'Robbery (avg/yr)': 'Raubüberfälle (Dichte)',
'Vehicle crime (avg/yr)': 'Fahrzeugkriminalität (Dichte)',
'Anti-social behaviour (avg/yr)': 'Antisoziales Verhalten (Dichte)',
'Criminal damage and arson (avg/yr)': 'Sachbeschädigung und Brandstiftung (Dichte)',
'Other theft (avg/yr)': 'Sonstiger Diebstahl (Dichte)',
'Theft from the person (avg/yr)': 'Diebstahl von der Person (Dichte)',
'Shoplifting (avg/yr)': 'Ladendiebstahl (Dichte)',
'Bicycle theft (avg/yr)': 'Fahrraddiebstahl (Dichte)',
'Drugs (avg/yr)': 'Drogendelikte (Dichte)',
'Possession of weapons (avg/yr)': 'Waffenbesitz (Dichte)',
'Public order (avg/yr)': 'Störung der öffentlichen Ordnung (Dichte)',
'Other crime (avg/yr)': 'Sonstige Straftaten (Dichte)',
// ─ Feature names (Neighbours) ─
'Median age': 'Medianalter',

View file

@ -732,6 +732,10 @@ const en = {
noChangeTitle: 'No-change journeys only',
noChangeDesc:
'Restricts to journeys with <strong>no transfers</strong> —walk, board one transit service, then walk to the destination. Useful when you want a single straight-through commute.',
oneChange: 'One change',
oneChangeTitle: 'One-change journeys',
oneChangeDesc:
'Allows journeys with <strong>at most one transfer</strong> —so a single change between two transit services is fine, but no more. A middle ground between no-change and unrestricted journeys.',
noBuses: 'No buses',
noBusesTitle: 'Excluding buses',
noBusesDesc:
@ -1518,22 +1522,22 @@ const en = {
'Air Quality and Road Safety Score': 'Air Quality and Road Safety Score',
// ─ Feature names (Crime) ─
'Serious crime (avg/yr)': 'Serious crime (avg/yr)',
'Minor crime (avg/yr)': 'Minor crime (avg/yr)',
'Violence and sexual offences (avg/yr)': 'Violence and sexual offences (avg/yr)',
'Burglary (avg/yr)': 'Burglary (avg/yr)',
'Robbery (avg/yr)': 'Robbery (avg/yr)',
'Vehicle crime (avg/yr)': 'Vehicle crime (avg/yr)',
'Anti-social behaviour (avg/yr)': 'Anti-social behaviour (avg/yr)',
'Criminal damage and arson (avg/yr)': 'Criminal damage and arson (avg/yr)',
'Other theft (avg/yr)': 'Other theft (avg/yr)',
'Theft from the person (avg/yr)': 'Theft from the person (avg/yr)',
'Shoplifting (avg/yr)': 'Shoplifting (avg/yr)',
'Bicycle theft (avg/yr)': 'Bicycle theft (avg/yr)',
'Drugs (avg/yr)': 'Drugs (avg/yr)',
'Possession of weapons (avg/yr)': 'Possession of weapons (avg/yr)',
'Public order (avg/yr)': 'Public order (avg/yr)',
'Other crime (avg/yr)': 'Other crime (avg/yr)',
'Serious crime (avg/yr)': 'Serious crime (density)',
'Minor crime (avg/yr)': 'Minor crime (density)',
'Violence and sexual offences (avg/yr)': 'Violence and sexual offences (density)',
'Burglary (avg/yr)': 'Burglary (density)',
'Robbery (avg/yr)': 'Robbery (density)',
'Vehicle crime (avg/yr)': 'Vehicle crime (density)',
'Anti-social behaviour (avg/yr)': 'Anti-social behaviour (density)',
'Criminal damage and arson (avg/yr)': 'Criminal damage and arson (density)',
'Other theft (avg/yr)': 'Other theft (density)',
'Theft from the person (avg/yr)': 'Theft from the person (density)',
'Shoplifting (avg/yr)': 'Shoplifting (density)',
'Bicycle theft (avg/yr)': 'Bicycle theft (density)',
'Drugs (avg/yr)': 'Drugs (density)',
'Possession of weapons (avg/yr)': 'Possession of weapons (density)',
'Public order (avg/yr)': 'Public order (density)',
'Other crime (avg/yr)': 'Other crime (density)',
// ─ Feature names (Neighbours) ─
'Median age': 'Median age',

View file

@ -760,6 +760,10 @@ const fr: Translations = {
noChangeTitle: 'Trajets directs uniquement',
noChangeDesc:
'Limite aux trajets <strong>sans correspondance</strong> : marche, un seul service de transport en commun, puis marche jusquà destination.',
oneChange: 'Une correspondance',
oneChangeTitle: 'Trajets avec une correspondance',
oneChangeDesc:
'Autorise les trajets avec <strong>au plus une correspondance</strong> : un seul changement entre deux services est permis, pas davantage. Un compromis entre les trajets directs et sans restriction.',
noBuses: 'Sans bus',
noBusesTitle: 'Exclure les bus',
noBusesDesc:
@ -1563,22 +1567,22 @@ const fr: Translations = {
'Air Quality and Road Safety Score': 'Score qualité de lair et sécurité routière',
// ─ Feature names (Crime) ─
'Serious crime (avg/yr)': 'Infractions graves (moy./an)',
'Minor crime (avg/yr)': 'Infractions mineures (moy./an)',
'Violence and sexual offences (avg/yr)': 'Violences et infractions sexuelles (moy./an)',
'Burglary (avg/yr)': 'Cambriolages (moy./an)',
'Robbery (avg/yr)': 'Vols avec violence (moy./an)',
'Vehicle crime (avg/yr)': 'Infractions liées aux véhicules (moy./an)',
'Anti-social behaviour (avg/yr)': 'Comportements antisociaux (moy./an)',
'Criminal damage and arson (avg/yr)': 'Dégradations et incendies criminels (moy./an)',
'Other theft (avg/yr)': 'Autres vols (moy./an)',
'Theft from the person (avg/yr)': 'Vols à la personne (moy./an)',
'Shoplifting (avg/yr)': 'Vols à létalage (moy./an)',
'Bicycle theft (avg/yr)': 'Vols de vélos (moy./an)',
'Drugs (avg/yr)': 'Infractions liées aux stupéfiants (moy./an)',
'Possession of weapons (avg/yr)': 'Possession darmes (moy./an)',
'Public order (avg/yr)': 'Troubles à lordre public (moy./an)',
'Other crime (avg/yr)': 'Autres infractions (moy./an)',
'Serious crime (avg/yr)': 'Infractions graves (densité)',
'Minor crime (avg/yr)': 'Infractions mineures (densité)',
'Violence and sexual offences (avg/yr)': 'Violences et infractions sexuelles (densité)',
'Burglary (avg/yr)': 'Cambriolages (densité)',
'Robbery (avg/yr)': 'Vols avec violence (densité)',
'Vehicle crime (avg/yr)': 'Infractions liées aux véhicules (densité)',
'Anti-social behaviour (avg/yr)': 'Comportements antisociaux (densité)',
'Criminal damage and arson (avg/yr)': 'Dégradations et incendies criminels (densité)',
'Other theft (avg/yr)': 'Autres vols (densité)',
'Theft from the person (avg/yr)': 'Vols à la personne (densité)',
'Shoplifting (avg/yr)': 'Vols à létalage (densité)',
'Bicycle theft (avg/yr)': 'Vols de vélos (densité)',
'Drugs (avg/yr)': 'Infractions liées aux stupéfiants (densité)',
'Possession of weapons (avg/yr)': 'Possession darmes (densité)',
'Public order (avg/yr)': 'Troubles à lordre public (densité)',
'Other crime (avg/yr)': 'Autres infractions (densité)',
// ─ Feature names (Neighbours) ─
'Median age': 'Âge médian',

View file

@ -725,6 +725,10 @@ const hi: Translations = {
noChangeTitle: 'केवल सीधी यात्राएँ',
noChangeDesc:
'<strong>बिना बदलाव</strong> वाली यात्राओं तक सीमित — पैदल चलें, एक सार्वजनिक परिवहन सेवा लें, फिर गंतव्य तक पैदल जाएं. सीधे आवागमन के लिए उपयोगी.',
oneChange: 'एक बदलाव',
oneChangeTitle: 'एक बदलाव वाली यात्राएँ',
oneChangeDesc:
'<strong>अधिकतम एक बदलाव</strong> वाली यात्राओं की अनुमति देता है — दो परिवहन सेवाओं के बीच एक बदलाव ठीक है, इससे अधिक नहीं. बिना बदलाव और असीमित यात्राओं के बीच का विकल्प.',
noBuses: 'बसों के बिना',
noBusesTitle: 'बसों को बाहर रखें',
noBusesDesc:
@ -1468,22 +1472,22 @@ const hi: Translations = {
'Health Deprivation and Disability Score': 'स्वास्थ्य वंचना और विकलांगता स्कोर',
'Housing Conditions Score': 'आवास स्थिति स्कोर',
'Air Quality and Road Safety Score': 'हवा की गुणवत्ता और सड़क सुरक्षा स्कोर',
'Serious crime (avg/yr)': 'गंभीर अपराध (औसत/वर्ष)',
'Minor crime (avg/yr)': 'मामूली अपराध (औसत/वर्ष)',
'Violence and sexual offences (avg/yr)': 'हिंसा और यौन अपराध (औसत/वर्ष)',
'Burglary (avg/yr)': 'सेंधमारी (औसत/वर्ष)',
'Robbery (avg/yr)': 'लूट (औसत/वर्ष)',
'Vehicle crime (avg/yr)': 'वाहन अपराध (औसत/वर्ष)',
'Anti-social behaviour (avg/yr)': 'असामाजिक व्यवहार (औसत/वर्ष)',
'Criminal damage and arson (avg/yr)': 'आपराधिक क्षति और आगजनी (औसत/वर्ष)',
'Other theft (avg/yr)': 'अन्य चोरी (औसत/वर्ष)',
'Theft from the person (avg/yr)': 'व्यक्ति से चोरी (औसत/वर्ष)',
'Shoplifting (avg/yr)': 'दुकान से चोरी (औसत/वर्ष)',
'Bicycle theft (avg/yr)': 'साइकिल चोरी (औसत/वर्ष)',
'Drugs (avg/yr)': 'ड्रग्स (औसत/वर्ष)',
'Possession of weapons (avg/yr)': 'हथियार रखने के अपराध (औसत/वर्ष)',
'Public order (avg/yr)': 'सार्वजनिक व्यवस्था अपराध (औसत/वर्ष)',
'Other crime (avg/yr)': 'अन्य अपराध (औसत/वर्ष)',
'Serious crime (avg/yr)': 'गंभीर अपराध (घनत्व)',
'Minor crime (avg/yr)': 'मामूली अपराध (घनत्व)',
'Violence and sexual offences (avg/yr)': 'हिंसा और यौन अपराध (घनत्व)',
'Burglary (avg/yr)': 'सेंधमारी (घनत्व)',
'Robbery (avg/yr)': 'लूट (घनत्व)',
'Vehicle crime (avg/yr)': 'वाहन अपराध (घनत्व)',
'Anti-social behaviour (avg/yr)': 'असामाजिक व्यवहार (घनत्व)',
'Criminal damage and arson (avg/yr)': 'आपराधिक क्षति और आगजनी (घनत्व)',
'Other theft (avg/yr)': 'अन्य चोरी (घनत्व)',
'Theft from the person (avg/yr)': 'व्यक्ति से चोरी (घनत्व)',
'Shoplifting (avg/yr)': 'दुकान से चोरी (घनत्व)',
'Bicycle theft (avg/yr)': 'साइकिल चोरी (घनत्व)',
'Drugs (avg/yr)': 'ड्रग्स (घनत्व)',
'Possession of weapons (avg/yr)': 'हथियार रखने के अपराध (घनत्व)',
'Public order (avg/yr)': 'सार्वजनिक व्यवस्था अपराध (घनत्व)',
'Other crime (avg/yr)': 'अन्य अपराध (घनत्व)',
'Median age': 'मध्य आयु',
'% White': '% श्वेत',
'% South Asian': '% दक्षिण एशियाई',

View file

@ -749,6 +749,10 @@ const hu: Translations = {
noChangeTitle: 'Csak közvetlen járatok',
noChangeDesc:
'<strong>Átszállás nélküli</strong> utazásokra korlátoz: gyaloglás, egy közlekedési eszköz, majd gyaloglás a célig.',
oneChange: 'Egy átszállás',
oneChangeTitle: 'Egy átszállásos utazások',
oneChangeDesc:
'<strong>Legfeljebb egy átszállást</strong> engedélyez: két közlekedési eszköz közötti egyetlen váltás megengedett, több nem. Köztes megoldás az átszállás nélküli és a korlátozás nélküli utazások között.',
noBuses: 'Busz nélkül',
noBusesTitle: 'Buszok kizárása',
noBusesDesc:
@ -1542,22 +1546,22 @@ const hu: Translations = {
'Air Quality and Road Safety Score': 'Levegőminőség és közlekedésbiztonság pontszám',
// ─ Feature names (Crime) ─
'Serious crime (avg/yr)': 'Súlyos bűncselekmény (éves átlag)',
'Minor crime (avg/yr)': 'Kisebb bűncselekmény (éves átlag)',
'Violence and sexual offences (avg/yr)': 'Erőszak és szexuális bűncselekmények (éves átlag)',
'Burglary (avg/yr)': 'Betörés (éves átlag)',
'Robbery (avg/yr)': 'Rablás (éves átlag)',
'Vehicle crime (avg/yr)': 'Járművel kapcsolatos bűncselekmények (éves átlag)',
'Anti-social behaviour (avg/yr)': 'Közösségellenes magatartás (éves átlag)',
'Criminal damage and arson (avg/yr)': 'Rongálás és gyújtogatás (éves átlag)',
'Other theft (avg/yr)': 'Egyéb lopás (éves átlag)',
'Theft from the person (avg/yr)': 'Személytől történő lopás (éves átlag)',
'Shoplifting (avg/yr)': 'Bolti lopás (éves átlag)',
'Bicycle theft (avg/yr)': 'Kerékpárlopás (éves átlag)',
'Drugs (avg/yr)': 'Kábítószer (éves átlag)',
'Possession of weapons (avg/yr)': 'Fegyverbirtoklás (éves átlag)',
'Public order (avg/yr)': 'Közrend (éves átlag)',
'Other crime (avg/yr)': 'Egyéb bűncselekmény (éves átlag)',
'Serious crime (avg/yr)': 'Súlyos bűncselekmény (sűrűség)',
'Minor crime (avg/yr)': 'Kisebb bűncselekmény (sűrűség)',
'Violence and sexual offences (avg/yr)': 'Erőszak és szexuális bűncselekmények (sűrűség)',
'Burglary (avg/yr)': 'Betörés (sűrűség)',
'Robbery (avg/yr)': 'Rablás (sűrűség)',
'Vehicle crime (avg/yr)': 'Járművel kapcsolatos bűncselekmények (sűrűség)',
'Anti-social behaviour (avg/yr)': 'Közösségellenes magatartás (sűrűség)',
'Criminal damage and arson (avg/yr)': 'Rongálás és gyújtogatás (sűrűség)',
'Other theft (avg/yr)': 'Egyéb lopás (sűrűség)',
'Theft from the person (avg/yr)': 'Személytől történő lopás (sűrűség)',
'Shoplifting (avg/yr)': 'Bolti lopás (sűrűség)',
'Bicycle theft (avg/yr)': 'Kerékpárlopás (sűrűség)',
'Drugs (avg/yr)': 'Kábítószer (sűrűség)',
'Possession of weapons (avg/yr)': 'Fegyverbirtoklás (sűrűség)',
'Public order (avg/yr)': 'Közrend (sűrűség)',
'Other crime (avg/yr)': 'Egyéb bűncselekmény (sűrűség)',
// ─ Feature names (Neighbours) ─
'Median age': 'Medián életkor',

View file

@ -695,6 +695,10 @@ const zh: Translations = {
noChangeTitle: '仅不换乘行程',
noChangeDesc:
'仅限<strong>不换乘</strong>行程:步行、乘坐一次公共交通,再步行到目的地。适合希望一路直达的通勤。',
oneChange: '一次换乘',
oneChangeTitle: '一次换乘行程',
oneChangeDesc:
'允许<strong>最多换乘一次</strong>的行程:在两种公共交通之间换乘一次可以,但不能更多。介于不换乘与不限换乘之间的折中选择。',
noBuses: '不坐公交',
noBusesTitle: '排除公交',
noBusesDesc:
@ -1453,22 +1457,22 @@ const zh: Translations = {
'Air Quality and Road Safety Score': '空气质量与道路安全得分',
// ─ Feature names (Crime) ─
'Serious crime (avg/yr)': '严重犯罪(年均',
'Minor crime (avg/yr)': '轻微犯罪(年均',
'Violence and sexual offences (avg/yr)': '暴力和性犯罪(年均',
'Burglary (avg/yr)': '入室盗窃(年均',
'Robbery (avg/yr)': '抢劫(年均',
'Vehicle crime (avg/yr)': '车辆犯罪(年均',
'Anti-social behaviour (avg/yr)': '反社会行为(年均',
'Criminal damage and arson (avg/yr)': '刑事毁坏和纵火(年均',
'Other theft (avg/yr)': '其他盗窃(年均',
'Theft from the person (avg/yr)': '人身盗窃(年均',
'Shoplifting (avg/yr)': '商店盗窃(年均',
'Bicycle theft (avg/yr)': '自行车盗窃(年均',
'Drugs (avg/yr)': '毒品犯罪(年均',
'Possession of weapons (avg/yr)': '非法持有武器(年均',
'Public order (avg/yr)': '扰乱公共秩序(年均',
'Other crime (avg/yr)': '其他犯罪(年均',
'Serious crime (avg/yr)': '严重犯罪(密度',
'Minor crime (avg/yr)': '轻微犯罪(密度',
'Violence and sexual offences (avg/yr)': '暴力和性犯罪(密度',
'Burglary (avg/yr)': '入室盗窃(密度',
'Robbery (avg/yr)': '抢劫(密度',
'Vehicle crime (avg/yr)': '车辆犯罪(密度',
'Anti-social behaviour (avg/yr)': '反社会行为(密度',
'Criminal damage and arson (avg/yr)': '刑事毁坏和纵火(密度',
'Other theft (avg/yr)': '其他盗窃(密度',
'Theft from the person (avg/yr)': '人身盗窃(密度',
'Shoplifting (avg/yr)': '商店盗窃(密度',
'Bicycle theft (avg/yr)': '自行车盗窃(密度',
'Drugs (avg/yr)': '毒品犯罪(密度',
'Possession of weapons (avg/yr)': '非法持有武器(密度',
'Public order (avg/yr)': '扰乱公共秩序(密度',
'Other crime (avg/yr)': '其他犯罪(密度',
// ─ Feature names (Neighbours) ─
'Median age': '中位年龄',

View file

@ -237,7 +237,7 @@ export const STACKED_GROUPS: Record<
{
label: 'Serious crime',
feature: 'Serious crime (avg/yr)',
unit: 'avg/yr',
unit: '',
components: [
'Violence and sexual offences (avg/yr)',
'Robbery (avg/yr)',
@ -248,7 +248,7 @@ export const STACKED_GROUPS: Record<
{
label: 'Minor crime',
feature: 'Minor crime (avg/yr)',
unit: 'avg/yr',
unit: '',
components: [
'Anti-social behaviour (avg/yr)',
'Criminal damage and arson (avg/yr)',