Replace map grids with hierarchical clustering
This commit is contained in:
parent
762b72100d
commit
ee6a1f7e70
|
|
@ -1,8 +1,10 @@
|
|||
import maplibregl from "maplibre-gl"
|
||||
import {
|
||||
clusterCountLabel,
|
||||
clusterExpansionTarget,
|
||||
clusterSizeClass,
|
||||
mergeNearbyMapPoints
|
||||
} from "./map_cluster_points.mjs"
|
||||
pointFeatureCollection
|
||||
} from "./map_cluster_features.mjs"
|
||||
|
||||
const trackingMinTimeMs = 5000
|
||||
|
||||
|
|
@ -152,22 +154,6 @@ const areaFeatureCollection = points => ({
|
|||
.map(areaPolygon)
|
||||
})
|
||||
|
||||
const clusterFeatureCollection = points => ({
|
||||
type: "FeatureCollection",
|
||||
features: points
|
||||
.filter(point => point.type === "cluster")
|
||||
.map(point => ({
|
||||
type: "Feature",
|
||||
id: point.id,
|
||||
properties: {
|
||||
id: point.id,
|
||||
count: point.count,
|
||||
urgent_count: point.urgent_count || 0
|
||||
},
|
||||
geometry: {type: "Point", coordinates: [point.longitude, point.latitude]}
|
||||
}))
|
||||
})
|
||||
|
||||
const mapViewport = map => {
|
||||
const bounds = map.getBounds()
|
||||
const container = map.getContainer()
|
||||
|
|
@ -300,7 +286,10 @@ const createAidMap = element => {
|
|||
resizeObserver: null,
|
||||
markers: [],
|
||||
markerElements: new Map(),
|
||||
points: [],
|
||||
selectedItemId: null,
|
||||
clusterExpansionPending: false,
|
||||
pendingMarkerRender: false,
|
||||
initializedViewport: false,
|
||||
locationBounds: null,
|
||||
recenterControl: null,
|
||||
|
|
@ -310,7 +299,9 @@ const createAidMap = element => {
|
|||
areaSelectedId: `${element.id || "aid-map"}-area-selected`,
|
||||
clusterSourceId: `${element.id || "aid-map"}-clusters`,
|
||||
clusterCircleId: `${element.id || "aid-map"}-cluster-circle`,
|
||||
clusterCountId: `${element.id || "aid-map"}-cluster-count`,
|
||||
serverClusterCircleId: `${element.id || "aid-map"}-server-cluster-circle`,
|
||||
pointCircleId: `${element.id || "aid-map"}-point-circle`,
|
||||
pointSelectedId: `${element.id || "aid-map"}-point-selected`,
|
||||
statusBadge: element.parentElement?.querySelector("[data-map-status-badge]") || null
|
||||
}
|
||||
|
||||
|
|
@ -458,6 +449,8 @@ const createAidMap = element => {
|
|||
}
|
||||
|
||||
state.highlightRequest = id => {
|
||||
state.selectedItemId = id || null
|
||||
|
||||
state.markerElements.forEach((element, requestId) => {
|
||||
element.classList.toggle("is-selected", requestId === id)
|
||||
})
|
||||
|
|
@ -465,6 +458,10 @@ const createAidMap = element => {
|
|||
if (state.map?.getLayer(state.areaSelectedId)) {
|
||||
state.map.setFilter(state.areaSelectedId, ["==", ["get", "id"], id || ""])
|
||||
}
|
||||
|
||||
if (state.map?.getLayer(state.pointSelectedId)) {
|
||||
state.map.setFilter(state.pointSelectedId, ["==", ["get", "id"], id || ""])
|
||||
}
|
||||
}
|
||||
|
||||
state.selectRequest = id => {
|
||||
|
|
@ -486,7 +483,6 @@ const createAidMap = element => {
|
|||
const current = state.element.getBoundingClientRect()
|
||||
if (current.width >= 160 && current.height >= 160) {
|
||||
state.map.resize()
|
||||
if (state.element.dataset.discoveryMap === "true") state.renderMarkers()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -517,6 +513,181 @@ const createAidMap = element => {
|
|||
return true
|
||||
}
|
||||
|
||||
state.expandNativeCluster = feature => {
|
||||
const source = state.map?.getSource(state.clusterSourceId)
|
||||
const clusterId = Number(feature?.properties?.cluster_id)
|
||||
if (!source || !Number.isFinite(clusterId)) return
|
||||
|
||||
Promise.resolve(source.getClusterExpansionZoom(clusterId)).then(expansionZoom => {
|
||||
if (!state.active || !state.map) return
|
||||
|
||||
const currentZoom = state.map.getZoom()
|
||||
const targetZoom = clusterExpansionTarget(currentZoom, Number(expansionZoom))
|
||||
|
||||
if (currentZoom >= 21.75 && targetZoom <= 22) {
|
||||
const count = Number(
|
||||
feature.properties?.total_count || feature.properties?.point_count
|
||||
) || 1
|
||||
const label = (state.element.dataset.clusterLabel || "%{count} items")
|
||||
.replace("%{count}", new Intl.NumberFormat(uiLocale()).format(count))
|
||||
|
||||
new maplibregl.Popup({offset: 18})
|
||||
.setLngLat(feature.geometry.coordinates)
|
||||
.setDOMContent(state.popupContent({
|
||||
title: label,
|
||||
location: state.element.dataset.clusterMaxZoomLabel || ""
|
||||
}))
|
||||
.addTo(state.map)
|
||||
return
|
||||
}
|
||||
|
||||
state.clusterExpansionPending = true
|
||||
state.map.easeTo({
|
||||
center: feature.geometry.coordinates,
|
||||
zoom: targetZoom,
|
||||
duration: 300
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
state.expandServerCluster = feature => {
|
||||
if (!feature || !state.map) return
|
||||
|
||||
const count = Number(feature.properties?.weight) || 1
|
||||
const requestedZoom = Number(feature.properties?.expansion_zoom)
|
||||
const currentZoom = state.map.getZoom()
|
||||
const expansionZoom = clusterExpansionTarget(
|
||||
currentZoom,
|
||||
Number.isFinite(requestedZoom) ? requestedZoom : currentZoom + 2
|
||||
)
|
||||
|
||||
if (currentZoom >= 21.75 && expansionZoom <= 22) {
|
||||
const label = (state.element.dataset.clusterLabel || "%{count} items")
|
||||
.replace("%{count}", String(count))
|
||||
|
||||
new maplibregl.Popup({offset: 18})
|
||||
.setLngLat(feature.geometry.coordinates)
|
||||
.setDOMContent(state.popupContent({
|
||||
title: label,
|
||||
location: state.element.dataset.clusterMaxZoomLabel || ""
|
||||
}))
|
||||
.addTo(state.map)
|
||||
return
|
||||
}
|
||||
|
||||
state.clusterExpansionPending = true
|
||||
state.map.easeTo({
|
||||
center: feature.geometry.coordinates,
|
||||
zoom: expansionZoom,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
|
||||
state.syncClusterMarkers = () => {
|
||||
state.markers.forEach(marker => marker.remove())
|
||||
state.markers = []
|
||||
|
||||
if (
|
||||
!state.map ||
|
||||
state.element.dataset.discoveryMap !== "true" ||
|
||||
!state.map.getLayer(state.clusterCircleId) ||
|
||||
!state.map.getLayer(state.serverClusterCircleId)
|
||||
) return
|
||||
|
||||
const features = state.map.queryRenderedFeatures({
|
||||
layers: [state.clusterCircleId, state.serverClusterCircleId]
|
||||
})
|
||||
const seen = new Set()
|
||||
|
||||
features.forEach(feature => {
|
||||
const nativeCluster =
|
||||
feature.properties?.cluster === true || feature.properties?.cluster === "true"
|
||||
const key = nativeCluster
|
||||
? `native:${feature.properties.cluster_id}`
|
||||
: `server:${feature.properties?.server_cluster_id || feature.properties?.id}`
|
||||
|
||||
if (seen.has(key)) return
|
||||
seen.add(key)
|
||||
|
||||
const count = Number(
|
||||
nativeCluster
|
||||
? feature.properties?.total_count || feature.properties?.point_count
|
||||
: feature.properties?.weight
|
||||
) || 1
|
||||
const urgentCount = Number(
|
||||
nativeCluster
|
||||
? feature.properties?.urgent_count
|
||||
: feature.properties?.urgent_weight
|
||||
) || 0
|
||||
const button = document.createElement("button")
|
||||
|
||||
button.type = "button"
|
||||
button.className = `request-map-cluster ${clusterSizeClass(count)}`
|
||||
button.dataset.clusterCount = String(count)
|
||||
button.classList.toggle("is-urgent", urgentCount > 0)
|
||||
button.textContent = clusterCountLabel(count, uiLocale())
|
||||
const formattedCount = new Intl.NumberFormat(uiLocale()).format(count)
|
||||
button.setAttribute(
|
||||
"aria-label",
|
||||
(state.element.dataset.clusterLabel || "%{count} items")
|
||||
.replace("%{count}", formattedCount)
|
||||
)
|
||||
button.title = button.getAttribute("aria-label")
|
||||
button.addEventListener("click", event => {
|
||||
event.stopPropagation()
|
||||
if (nativeCluster) {
|
||||
state.expandNativeCluster(feature)
|
||||
} else {
|
||||
state.expandServerCluster(feature)
|
||||
}
|
||||
})
|
||||
|
||||
state.markers.push(
|
||||
new maplibregl.Marker({element: button})
|
||||
.setLngLat(feature.geometry.coordinates)
|
||||
.addTo(state.map)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
state.syncVisibleAreas = () => {
|
||||
if (!state.map) return
|
||||
|
||||
const areaSource = state.map.getSource(state.areaSourceId)
|
||||
if (!areaSource) return
|
||||
|
||||
let visiblePoints = state.points
|
||||
|
||||
if (state.element.dataset.discoveryMap === "true") {
|
||||
if (!state.map.getSource(state.clusterSourceId)?.loaded()) return
|
||||
|
||||
const visibleIds = new Set(
|
||||
state.map
|
||||
.querySourceFeatures(state.clusterSourceId, {
|
||||
filter: [
|
||||
"all",
|
||||
["!", ["has", "point_count"]],
|
||||
["!=", ["get", "item_type"], "cluster"]
|
||||
]
|
||||
})
|
||||
.map(feature => feature.properties?.id)
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
visiblePoints = state.points.filter(point => visibleIds.has(point.id))
|
||||
}
|
||||
|
||||
const signature = visiblePoints
|
||||
.filter(point => !point.exact && Number.isFinite(point.radius_meters))
|
||||
.map(point => point.id)
|
||||
.sort()
|
||||
.join(":")
|
||||
|
||||
if (signature === state.visibleAreaSignature) return
|
||||
state.visibleAreaSignature = signature
|
||||
areaSource.setData(areaFeatureCollection(visiblePoints))
|
||||
}
|
||||
|
||||
state.renderMarkers = () => {
|
||||
if (!state.map) return
|
||||
|
||||
|
|
@ -525,22 +696,45 @@ const createAidMap = element => {
|
|||
state.markerElements.clear()
|
||||
|
||||
const points = markerPoints(state.element)
|
||||
const displayPoints = mergeNearbyMapPoints(state.map, points)
|
||||
const exactPoints = displayPoints.filter(
|
||||
point => point.type !== "cluster" && (point.exact || !Number.isFinite(point.radius_meters))
|
||||
)
|
||||
const clusterPoints = displayPoints.filter(point => point.type === "cluster")
|
||||
const areaFeatures = areaFeatureCollection(displayPoints)
|
||||
const clusterFeatures = clusterFeatureCollection(clusterPoints)
|
||||
state.points = points
|
||||
const pointFeatures = pointFeatureCollection(points)
|
||||
const areaFeatures =
|
||||
state.element.dataset.discoveryMap === "true"
|
||||
? emptyFeatureCollection()
|
||||
: areaFeatureCollection(points)
|
||||
const bounds = new maplibregl.LngLatBounds()
|
||||
|
||||
exactPoints.forEach(point => bounds.extend([point.longitude, point.latitude]))
|
||||
areaFeatures.features.forEach(feature => extendBoundsWithFeature(bounds, feature))
|
||||
clusterFeatures.features.forEach(feature => bounds.extend(feature.geometry.coordinates))
|
||||
state.locationBounds = displayPoints.length > 0 ? bounds : null
|
||||
points.forEach(point => {
|
||||
const clusterBounds = point.bounds
|
||||
|
||||
if (state.map.isStyleLoaded()) {
|
||||
const areaSource = state.map.getSource(state.areaSourceId)
|
||||
if (
|
||||
clusterBounds &&
|
||||
[clusterBounds.west, clusterBounds.south, clusterBounds.east, clusterBounds.north]
|
||||
.every(value => Number.isFinite(Number(value)))
|
||||
) {
|
||||
bounds.extend([Number(clusterBounds.west), Number(clusterBounds.south)])
|
||||
bounds.extend([Number(clusterBounds.east), Number(clusterBounds.north)])
|
||||
} else {
|
||||
bounds.extend([point.longitude, point.latitude])
|
||||
}
|
||||
})
|
||||
state.locationBounds = points.length > 0 ? bounds : null
|
||||
|
||||
const existingAreaSource = state.map.getSource(state.areaSourceId)
|
||||
const existingPointSource = state.map.getSource(state.clusterSourceId)
|
||||
|
||||
if (
|
||||
(!existingAreaSource || !existingPointSource) &&
|
||||
!state.map.isStyleLoaded()
|
||||
) {
|
||||
state.pendingMarkerRender = true
|
||||
return
|
||||
}
|
||||
|
||||
state.pendingMarkerRender = false
|
||||
|
||||
{
|
||||
const areaSource = existingAreaSource
|
||||
|
||||
if (areaSource) {
|
||||
areaSource.setData(areaFeatures)
|
||||
|
|
@ -550,12 +744,14 @@ const createAidMap = element => {
|
|||
id: state.areaFillId,
|
||||
type: "fill",
|
||||
source: state.areaSourceId,
|
||||
minzoom: state.element.dataset.discoveryMap === "true" ? 12 : 0,
|
||||
paint: {"fill-color": "#278467", "fill-opacity": 0.18}
|
||||
})
|
||||
state.map.addLayer({
|
||||
id: state.areaLineId,
|
||||
type: "line",
|
||||
source: state.areaSourceId,
|
||||
minzoom: state.element.dataset.discoveryMap === "true" ? 12 : 0,
|
||||
paint: {
|
||||
"line-color": "#16725b",
|
||||
"line-width": 2,
|
||||
|
|
@ -566,6 +762,7 @@ const createAidMap = element => {
|
|||
id: state.areaSelectedId,
|
||||
type: "line",
|
||||
source: state.areaSourceId,
|
||||
minzoom: state.element.dataset.discoveryMap === "true" ? 12 : 0,
|
||||
filter: ["==", ["get", "id"], ""],
|
||||
paint: {"line-color": "#d6573b", "line-width": 5}
|
||||
})
|
||||
|
|
@ -588,83 +785,171 @@ const createAidMap = element => {
|
|||
})
|
||||
}
|
||||
|
||||
}
|
||||
const pointSource = existingPointSource
|
||||
|
||||
exactPoints.forEach(point => {
|
||||
const popup = new maplibregl.Popup({offset: 18}).setDOMContent(state.popupContent(point))
|
||||
const markerColor = point.type === "live" ? "#2563eb" : point.exact ? "#d6573b" : "#278467"
|
||||
const marker = new maplibregl.Marker({color: markerColor})
|
||||
.setLngLat([point.longitude, point.latitude])
|
||||
.setPopup(popup)
|
||||
.addTo(state.map)
|
||||
|
||||
const markerElement = marker.getElement()
|
||||
markerElement.dataset.markerType = point.type || "request"
|
||||
markerElement.setAttribute("aria-label", point.title || uiText("mapMarkerLabel"))
|
||||
|
||||
if (point.id) {
|
||||
markerElement.dataset.requestId = point.id
|
||||
markerElement.addEventListener("click", () => state.selectRequest(point.id))
|
||||
state.markerElements.set(point.id, markerElement)
|
||||
}
|
||||
|
||||
state.markers.push(marker)
|
||||
})
|
||||
|
||||
clusterPoints.forEach(point => {
|
||||
const element = document.createElement("button")
|
||||
element.type = "button"
|
||||
element.className = `request-map-cluster ${clusterSizeClass(point.count)}`
|
||||
element.dataset.clusterCount = String(point.count)
|
||||
element.classList.toggle("is-urgent", Number(point.urgent_count) > 0)
|
||||
element.textContent = String(point.count)
|
||||
element.setAttribute(
|
||||
"aria-label",
|
||||
(state.element.dataset.clusterLabel || "%{count} requests")
|
||||
.replace("%{count}", String(point.count))
|
||||
)
|
||||
element.title = element.getAttribute("aria-label")
|
||||
element.addEventListener("click", event => {
|
||||
event.stopPropagation()
|
||||
state.clusterExpansionPending = true
|
||||
|
||||
const bounds = point.bounds
|
||||
const hasBounds =
|
||||
bounds &&
|
||||
[bounds.west, bounds.south, bounds.east, bounds.north]
|
||||
.every(value => Number.isFinite(Number(value)))
|
||||
const hasArea =
|
||||
hasBounds &&
|
||||
(Math.abs(Number(bounds.east) - Number(bounds.west)) > 0.000001 ||
|
||||
Math.abs(Number(bounds.north) - Number(bounds.south)) > 0.000001)
|
||||
|
||||
if (hasArea) {
|
||||
state.map.fitBounds(
|
||||
[
|
||||
[Number(bounds.west), Number(bounds.south)],
|
||||
[Number(bounds.east), Number(bounds.north)]
|
||||
],
|
||||
{
|
||||
padding: 72,
|
||||
maxZoom: Math.min(state.map.getZoom() + 5, 18),
|
||||
duration: 300
|
||||
}
|
||||
)
|
||||
if (pointSource) {
|
||||
pointSource.setData(pointFeatures)
|
||||
} else {
|
||||
state.map.easeTo({
|
||||
center: [point.longitude, point.latitude],
|
||||
zoom: Math.min(state.map.getZoom() + 2, 20),
|
||||
duration: 300
|
||||
const sourceOptions = {
|
||||
type: "geojson",
|
||||
data: pointFeatures,
|
||||
generateId: false
|
||||
}
|
||||
|
||||
if (state.element.dataset.discoveryMap === "true") {
|
||||
Object.assign(sourceOptions, {
|
||||
maxzoom: 22,
|
||||
cluster: true,
|
||||
clusterRadius: 64,
|
||||
clusterMaxZoom: 21,
|
||||
clusterMinPoints: 2,
|
||||
clusterProperties: {
|
||||
total_count: ["+", ["get", "weight"]],
|
||||
urgent_count: ["+", ["get", "urgent_weight"]]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
state.map.addSource(state.clusterSourceId, sourceOptions)
|
||||
state.map.addLayer({
|
||||
id: state.clusterCircleId,
|
||||
type: "circle",
|
||||
source: state.clusterSourceId,
|
||||
filter: ["has", "point_count"],
|
||||
paint: {
|
||||
"circle-color": [
|
||||
"case",
|
||||
[">", ["coalesce", ["get", "urgent_count"], 0], 0],
|
||||
"#c2412d",
|
||||
"#087f5b"
|
||||
],
|
||||
"circle-radius": [
|
||||
"step",
|
||||
["coalesce", ["get", "total_count"], ["get", "point_count"]],
|
||||
22,
|
||||
100,
|
||||
25,
|
||||
1_000,
|
||||
28,
|
||||
10_000,
|
||||
31,
|
||||
100_000,
|
||||
34
|
||||
],
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 3,
|
||||
"circle-opacity": 0.96
|
||||
}
|
||||
})
|
||||
state.map.addLayer({
|
||||
id: state.serverClusterCircleId,
|
||||
type: "circle",
|
||||
source: state.clusterSourceId,
|
||||
filter: [
|
||||
"all",
|
||||
["!", ["has", "point_count"]],
|
||||
["==", ["get", "item_type"], "cluster"]
|
||||
],
|
||||
paint: {
|
||||
"circle-color": [
|
||||
"case",
|
||||
[">", ["coalesce", ["get", "urgent_weight"], 0], 0],
|
||||
"#c2412d",
|
||||
"#087f5b"
|
||||
],
|
||||
"circle-radius": [
|
||||
"step",
|
||||
["get", "weight"],
|
||||
22,
|
||||
100,
|
||||
25,
|
||||
1_000,
|
||||
28,
|
||||
10_000,
|
||||
31,
|
||||
100_000,
|
||||
34
|
||||
],
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 3,
|
||||
"circle-opacity": 0.96
|
||||
}
|
||||
})
|
||||
state.map.addLayer({
|
||||
id: state.pointCircleId,
|
||||
type: "circle",
|
||||
source: state.clusterSourceId,
|
||||
filter: [
|
||||
"all",
|
||||
["!", ["has", "point_count"]],
|
||||
["!=", ["get", "item_type"], "cluster"]
|
||||
],
|
||||
paint: {
|
||||
"circle-color": [
|
||||
"case",
|
||||
["==", ["get", "item_type"], "live"],
|
||||
"#2563eb",
|
||||
["==", ["get", "exact"], true],
|
||||
"#d6573b",
|
||||
"#278467"
|
||||
],
|
||||
"circle-radius": 9,
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 3
|
||||
}
|
||||
})
|
||||
state.map.addLayer({
|
||||
id: state.pointSelectedId,
|
||||
type: "circle",
|
||||
source: state.clusterSourceId,
|
||||
filter: ["==", ["get", "id"], state.selectedItemId || ""],
|
||||
paint: {
|
||||
"circle-color": "rgba(0,0,0,0)",
|
||||
"circle-radius": 15,
|
||||
"circle-stroke-color": "#111827",
|
||||
"circle-stroke-width": 3
|
||||
}
|
||||
})
|
||||
|
||||
const marker = new maplibregl.Marker({element})
|
||||
.setLngLat([point.longitude, point.latitude])
|
||||
.addTo(state.map)
|
||||
const interactiveLayers = [
|
||||
state.clusterCircleId,
|
||||
state.serverClusterCircleId,
|
||||
state.pointCircleId
|
||||
]
|
||||
|
||||
state.markers.push(marker)
|
||||
interactiveLayers.forEach(layerId => {
|
||||
state.map.on("mouseenter", layerId, () => {
|
||||
state.map.getCanvas().style.cursor = "pointer"
|
||||
})
|
||||
state.map.on("mouseleave", layerId, () => {
|
||||
state.map.getCanvas().style.cursor = ""
|
||||
})
|
||||
})
|
||||
|
||||
state.map.on("click", state.clusterCircleId, event => {
|
||||
const feature = event.features?.[0]
|
||||
if (feature) state.expandNativeCluster(feature)
|
||||
})
|
||||
|
||||
state.map.on("click", state.serverClusterCircleId, event => {
|
||||
const feature = event.features?.[0]
|
||||
if (feature) state.expandServerCluster(feature)
|
||||
})
|
||||
|
||||
state.map.on("click", state.pointCircleId, event => {
|
||||
const feature = event.features?.[0]
|
||||
const properties = feature?.properties
|
||||
if (!feature || !properties) return
|
||||
|
||||
if (properties.id) state.selectRequest(properties.id)
|
||||
|
||||
new maplibregl.Popup({offset: 18})
|
||||
.setLngLat(feature.geometry.coordinates)
|
||||
.setDOMContent(state.popupContent(properties))
|
||||
.addTo(state.map)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (state.element.dataset.autoFit !== "false") state.fitMarkers()
|
||||
}
|
||||
|
|
@ -752,7 +1037,6 @@ const createAidMap = element => {
|
|||
|
||||
state.onMoveEnd = () => {
|
||||
if (!state.initializedViewport || state.element.dataset.discoveryMap !== "true") return
|
||||
state.renderMarkers()
|
||||
const viewport = mapViewport(state.map)
|
||||
if (!viewport) return
|
||||
|
||||
|
|
@ -764,11 +1048,23 @@ const createAidMap = element => {
|
|||
}
|
||||
}
|
||||
|
||||
state.onIdle = () => {
|
||||
state.markReady()
|
||||
|
||||
if (state.pendingMarkerRender) {
|
||||
state.renderMarkers()
|
||||
if (state.pendingMarkerRender) return
|
||||
}
|
||||
|
||||
state.syncVisibleAreas()
|
||||
state.syncClusterMarkers()
|
||||
}
|
||||
|
||||
state.onRequestHighlight = event => state.highlightRequest(event.detail?.id)
|
||||
|
||||
state.map.on("dataloading", state.markLoading)
|
||||
state.map.on("load", state.onLoad)
|
||||
state.map.on("idle", state.markReady)
|
||||
state.map.on("idle", state.onIdle)
|
||||
state.map.on("moveend", state.onMoveEnd)
|
||||
window.addEventListener("wnh:request-highlight", state.onRequestHighlight)
|
||||
if ("ResizeObserver" in window) {
|
||||
|
|
@ -785,7 +1081,7 @@ const createAidMap = element => {
|
|||
state.resizeObserver?.disconnect()
|
||||
state.map?.off("dataloading", state.markLoading)
|
||||
state.map?.off("load", state.onLoad)
|
||||
state.map?.off("idle", state.markReady)
|
||||
state.map?.off("idle", state.onIdle)
|
||||
state.map?.off("moveend", state.onMoveEnd)
|
||||
window.removeEventListener("wnh:request-highlight", state.onRequestHighlight)
|
||||
state.map?.remove()
|
||||
|
|
|
|||
59
assets/js/map_cluster_features.mjs
Normal file
59
assets/js/map_cluster_features.mjs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
const finiteCount = (value, fallback) => {
|
||||
const count = Number(value)
|
||||
return Number.isFinite(count) && count >= 0 ? count : fallback
|
||||
}
|
||||
|
||||
export const clusterSizeClass = count => {
|
||||
if (count >= 100_000) return "is-xxl"
|
||||
if (count >= 10_000) return "is-xl"
|
||||
if (count >= 1_000) return "is-lg"
|
||||
return "is-md"
|
||||
}
|
||||
|
||||
export const clusterCountLabel = (count, locale = "en") => {
|
||||
const value = finiteCount(count, 0)
|
||||
|
||||
if (value < 1_000) return new Intl.NumberFormat(locale).format(value)
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: value < 10_000 ? 1 : 0
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
export const clusterExpansionTarget = (currentZoom, requestedZoom, maximumZoom = 22) => {
|
||||
const current = finiteCount(currentZoom, 0)
|
||||
const requested = finiteCount(requestedZoom, current + 1)
|
||||
const nearestIntegerZoom = Math.round(current)
|
||||
const normalizedCurrent =
|
||||
Math.abs(current - nearestIntegerZoom) < 0.001 ? nearestIntegerZoom : current
|
||||
const nextIntegerZoom = Math.floor(normalizedCurrent) + 1
|
||||
|
||||
return Math.min(maximumZoom, Math.max(nextIntegerZoom, requested))
|
||||
}
|
||||
|
||||
export const pointFeatureCollection = points => ({
|
||||
type: "FeatureCollection",
|
||||
features: points.map(point => ({
|
||||
type: "Feature",
|
||||
id: point.id,
|
||||
properties: {
|
||||
id: point.id,
|
||||
item_type: point.type || "request",
|
||||
title: point.title || "",
|
||||
location: point.location || "",
|
||||
exact: point.exact === true,
|
||||
radius_meters: Number.isFinite(point.radius_meters) ? point.radius_meters : 0,
|
||||
weight: finiteCount(point.count, 1),
|
||||
urgent_weight: finiteCount(point.urgent_count, 0),
|
||||
server_cluster_id: point.cluster_id || "",
|
||||
parent_cluster_id: point.parent_cluster_id || "",
|
||||
hierarchy_level: finiteCount(point.hierarchy_level, 0),
|
||||
expansion_zoom: finiteCount(point.expansion_zoom, 22)
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [Number(point.longitude), Number(point.latitude)]
|
||||
}
|
||||
}))
|
||||
})
|
||||
123
assets/js/map_cluster_features.test.mjs
Normal file
123
assets/js/map_cluster_features.test.mjs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import assert from "node:assert/strict"
|
||||
import test from "node:test"
|
||||
|
||||
import {Supercluster} from "@maplibre/geojson-vt"
|
||||
import {
|
||||
clusterCountLabel,
|
||||
clusterExpansionTarget,
|
||||
clusterSizeClass,
|
||||
pointFeatureCollection
|
||||
} from "./map_cluster_features.mjs"
|
||||
|
||||
const buildIndex = points => {
|
||||
const index = new Supercluster({
|
||||
radius: 64,
|
||||
maxZoom: 21,
|
||||
minPoints: 2,
|
||||
map: properties => ({
|
||||
total_count: properties.weight,
|
||||
urgent_count: properties.urgent_weight
|
||||
}),
|
||||
reduce: (accumulated, properties) => {
|
||||
accumulated.total_count += properties.total_count
|
||||
accumulated.urgent_count += properties.urgent_count
|
||||
}
|
||||
})
|
||||
|
||||
index.load(pointFeatureCollection(points).features)
|
||||
return index
|
||||
}
|
||||
|
||||
test("weighted server nodes preserve the represented record total", () => {
|
||||
const index = buildIndex([
|
||||
{id: "request:4:1:1", type: "cluster", longitude: 30.50, latitude: 50.45, count: 700},
|
||||
{
|
||||
id: "request:4:1:2",
|
||||
type: "cluster",
|
||||
longitude: 30.51,
|
||||
latitude: 50.46,
|
||||
count: 300,
|
||||
urgent_count: 10
|
||||
},
|
||||
{id: "one", type: "request", longitude: -74.0, latitude: 40.7}
|
||||
])
|
||||
|
||||
const world = index.getClusters([-180, -85, 180, 85], 0)
|
||||
const represented = world.reduce(
|
||||
(sum, feature) => sum + (feature.properties.total_count || feature.properties.weight),
|
||||
0
|
||||
)
|
||||
const urgent = world.reduce(
|
||||
(sum, feature) => sum + (feature.properties.urgent_count || feature.properties.urgent_weight),
|
||||
0
|
||||
)
|
||||
|
||||
assert.equal(represented, 1_001)
|
||||
assert.equal(urgent, 10)
|
||||
})
|
||||
|
||||
test("server hierarchy metadata survives GeoJSON conversion without becoming a native cluster", () => {
|
||||
const feature = pointFeatureCollection([
|
||||
{
|
||||
id: "request:11:1197:1378",
|
||||
type: "cluster",
|
||||
cluster_id: "request:11:1197:1378",
|
||||
parent_cluster_id: "request:10:598:689",
|
||||
hierarchy_level: 11,
|
||||
expansion_zoom: 13,
|
||||
longitude: 30.52,
|
||||
latitude: 50.45,
|
||||
count: 27
|
||||
}
|
||||
]).features[0]
|
||||
|
||||
assert.equal(feature.properties.server_cluster_id, "request:11:1197:1378")
|
||||
assert.equal(feature.properties.parent_cluster_id, "request:10:598:689")
|
||||
assert.equal(feature.properties.hierarchy_level, 11)
|
||||
assert.equal(feature.properties.expansion_zoom, 13)
|
||||
assert.equal(feature.properties.weight, 27)
|
||||
assert.equal("point_count" in feature.properties, false)
|
||||
})
|
||||
|
||||
test("native clusters expose an expansion zoom and eventually split unique coordinates", () => {
|
||||
const index = buildIndex([
|
||||
{id: "a", longitude: 30.52340, latitude: 50.45010},
|
||||
{id: "b", longitude: 30.52341, latitude: 50.45011},
|
||||
{id: "c", longitude: 30.60, latitude: 50.50}
|
||||
])
|
||||
|
||||
const world = index.getClusters([-180, -85, 180, 85], 0)
|
||||
const cluster = world.find(feature => feature.properties.cluster)
|
||||
|
||||
assert.ok(cluster)
|
||||
assert.ok(index.getClusterExpansionZoom(cluster.properties.cluster_id) > 0)
|
||||
assert.equal(
|
||||
index.getClusters([30.5, 50.4, 30.7, 50.6], 22)
|
||||
.filter(feature => !feature.properties.cluster)
|
||||
.length,
|
||||
3
|
||||
)
|
||||
})
|
||||
|
||||
test("cluster size classes remain stable for accessible count buttons", () => {
|
||||
assert.equal(clusterSizeClass(999), "is-md")
|
||||
assert.equal(clusterSizeClass(1_000), "is-lg")
|
||||
assert.equal(clusterSizeClass(10_000), "is-xl")
|
||||
assert.equal(clusterSizeClass(100_000), "is-xxl")
|
||||
})
|
||||
|
||||
test("large visual counts are compact while exact values remain available to accessibility text", () => {
|
||||
assert.equal(clusterCountLabel(999, "en"), "999")
|
||||
assert.equal(clusterCountLabel(1_250, "en"), "1.3K")
|
||||
assert.equal(clusterCountLabel(48_533, "en"), "49K")
|
||||
assert.equal(clusterCountLabel(1_000_000, "en"), "1M")
|
||||
})
|
||||
|
||||
test("cluster expansion always advances at least one integer zoom level", () => {
|
||||
assert.equal(clusterExpansionTarget(9, 9), 10)
|
||||
assert.equal(clusterExpansionTarget(8.999_999_096_835_52, 9), 10)
|
||||
assert.equal(clusterExpansionTarget(10.999_994_193_019_71, 11), 12)
|
||||
assert.equal(clusterExpansionTarget(9.6, 10), 10)
|
||||
assert.equal(clusterExpansionTarget(9.6, 13), 13)
|
||||
assert.equal(clusterExpansionTarget(21.9, 24), 22)
|
||||
})
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
const clusterCount = point => {
|
||||
const count = Number(point?.count)
|
||||
return Number.isFinite(count) && count > 0 ? count : 1
|
||||
}
|
||||
|
||||
const urgentCount = point => {
|
||||
const count = Number(point?.urgent_count)
|
||||
return Number.isFinite(count) && count > 0 ? count : 0
|
||||
}
|
||||
|
||||
export const clusterDiameterPixels = count => {
|
||||
if (count >= 100_000) return 60
|
||||
if (count >= 10_000) return 56
|
||||
if (count >= 1_000) return 52
|
||||
return 44
|
||||
}
|
||||
|
||||
export const clusterSizeClass = count => {
|
||||
if (count >= 100_000) return "is-xxl"
|
||||
if (count >= 10_000) return "is-xl"
|
||||
if (count >= 1_000) return "is-lg"
|
||||
return "is-md"
|
||||
}
|
||||
|
||||
const mergeDistance = (left, right, gap) =>
|
||||
(clusterDiameterPixels(left.count) + clusterDiameterPixels(right.count)) / 2 + gap
|
||||
|
||||
const coordinatesFromPixel = (map, pixel) => {
|
||||
const coordinates = map.unproject([pixel.x, pixel.y])
|
||||
|
||||
return {
|
||||
longitude: Number(coordinates.lng ?? coordinates[0]),
|
||||
latitude: Number(coordinates.lat ?? coordinates[1])
|
||||
}
|
||||
}
|
||||
|
||||
const finiteCoordinate = value => {
|
||||
const coordinate = Number(value)
|
||||
return Number.isFinite(coordinate) ? coordinate : null
|
||||
}
|
||||
|
||||
const pointBounds = point => {
|
||||
const longitude = finiteCoordinate(point?.longitude)
|
||||
const latitude = finiteCoordinate(point?.latitude)
|
||||
const source = point?.bounds || {}
|
||||
const west = finiteCoordinate(source.west) ?? longitude
|
||||
const south = finiteCoordinate(source.south) ?? latitude
|
||||
const east = finiteCoordinate(source.east) ?? longitude
|
||||
const north = finiteCoordinate(source.north) ?? latitude
|
||||
|
||||
if ([west, south, east, north].some(value => value === null)) return null
|
||||
|
||||
return {
|
||||
west: Math.min(west, east),
|
||||
south: Math.min(south, north),
|
||||
east: Math.max(west, east),
|
||||
north: Math.max(south, north)
|
||||
}
|
||||
}
|
||||
|
||||
const groupBounds = points => {
|
||||
const bounds = points.map(pointBounds).filter(Boolean)
|
||||
if (bounds.length === 0) return null
|
||||
|
||||
return bounds.reduce(
|
||||
(result, current) => ({
|
||||
west: Math.min(result.west, current.west),
|
||||
south: Math.min(result.south, current.south),
|
||||
east: Math.max(result.east, current.east),
|
||||
north: Math.max(result.north, current.north)
|
||||
}),
|
||||
bounds[0]
|
||||
)
|
||||
}
|
||||
|
||||
const nearbyMapPointGroups = (map, points, options = {}) => {
|
||||
const gap = Number.isFinite(options.gap) ? Math.max(options.gap, 0) : 8
|
||||
|
||||
const groups = points.map(point => {
|
||||
const pixel = map.project([point.longitude, point.latitude])
|
||||
|
||||
return {
|
||||
points: [point],
|
||||
x: pixel.x,
|
||||
y: pixel.y,
|
||||
count: clusterCount(point),
|
||||
urgent_count: urgentCount(point)
|
||||
}
|
||||
})
|
||||
|
||||
let merged = true
|
||||
|
||||
while (merged) {
|
||||
merged = false
|
||||
|
||||
outer:
|
||||
for (let leftIndex = 0; leftIndex < groups.length; leftIndex += 1) {
|
||||
for (let rightIndex = leftIndex + 1; rightIndex < groups.length; rightIndex += 1) {
|
||||
const left = groups[leftIndex]
|
||||
const right = groups[rightIndex]
|
||||
const distance = Math.hypot(left.x - right.x, left.y - right.y)
|
||||
|
||||
if (distance >= mergeDistance(left, right, gap)) continue
|
||||
|
||||
const count = left.count + right.count
|
||||
left.x = (left.x * left.count + right.x * right.count) / count
|
||||
left.y = (left.y * left.count + right.y * right.count) / count
|
||||
left.count = count
|
||||
left.urgent_count += right.urgent_count
|
||||
left.points.push(...right.points)
|
||||
groups.splice(rightIndex, 1)
|
||||
merged = true
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
const visualCluster = (map, group) => {
|
||||
const coordinates = coordinatesFromPixel(map, group)
|
||||
const firstId = group.points[0]?.id || "anonymous"
|
||||
|
||||
return {
|
||||
type: "cluster",
|
||||
id: `visual-cluster-${group.points.length}-${firstId}`,
|
||||
count: group.count,
|
||||
urgent_count: group.urgent_count,
|
||||
longitude: coordinates.longitude,
|
||||
latitude: coordinates.latitude,
|
||||
bounds: groupBounds(group.points)
|
||||
}
|
||||
}
|
||||
|
||||
export const mergeNearbyMapClusters = (map, points, options = {}) =>
|
||||
nearbyMapPointGroups(map, points, options).map(group => visualCluster(map, group))
|
||||
|
||||
export const mergeNearbyMapPoints = (map, points, options = {}) => {
|
||||
return nearbyMapPointGroups(map, points, options).map(group => {
|
||||
const point = group.points[0]
|
||||
|
||||
if (group.points.length === 1 && point?.type !== "cluster") return point
|
||||
|
||||
return visualCluster(map, group)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import assert from "node:assert/strict"
|
||||
import test from "node:test"
|
||||
|
||||
import {
|
||||
clusterDiameterPixels,
|
||||
clusterSizeClass,
|
||||
mergeNearbyMapClusters,
|
||||
mergeNearbyMapPoints
|
||||
} from "./map_cluster_points.mjs"
|
||||
|
||||
const flatMap = scale => ({
|
||||
project: ([longitude, latitude]) => ({x: longitude * scale, y: latitude * scale}),
|
||||
unproject: ([x, y]) => ({lng: x / scale, lat: y / scale})
|
||||
})
|
||||
|
||||
test("merges visually overlapping clusters and preserves their total", () => {
|
||||
const points = [
|
||||
{
|
||||
id: "a",
|
||||
type: "cluster",
|
||||
count: 100,
|
||||
urgent_count: 2,
|
||||
longitude: 0,
|
||||
latitude: 0,
|
||||
bounds: {west: -5, south: -2, east: 5, north: 2}
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
type: "cluster",
|
||||
count: 50,
|
||||
urgent_count: 1,
|
||||
longitude: 30,
|
||||
latitude: 0,
|
||||
bounds: {west: 25, south: -3, east: 35, north: 4}
|
||||
},
|
||||
{id: "c", type: "cluster", count: 7, urgent_count: 0, longitude: 140, latitude: 0}
|
||||
]
|
||||
|
||||
const result = mergeNearbyMapClusters(flatMap(1), points)
|
||||
|
||||
assert.equal(result.length, 2)
|
||||
assert.equal(result.reduce((total, cluster) => total + cluster.count, 0), 157)
|
||||
|
||||
const merged = result.find(cluster => cluster.count === 150)
|
||||
assert.equal(merged.urgent_count, 3)
|
||||
assert.equal(merged.longitude, 10)
|
||||
assert.equal(merged.latitude, 0)
|
||||
assert.deepEqual(merged.bounds, {west: -5, south: -3, east: 35, north: 4})
|
||||
})
|
||||
|
||||
test("splits the same clusters again at a closer zoom", () => {
|
||||
const points = [
|
||||
{id: "a", type: "cluster", count: 20, longitude: 0, latitude: 0},
|
||||
{id: "b", type: "cluster", count: 30, longitude: 30, latitude: 0}
|
||||
]
|
||||
|
||||
assert.equal(mergeNearbyMapClusters(flatMap(1), points).length, 1)
|
||||
assert.equal(mergeNearbyMapClusters(flatMap(3), points).length, 2)
|
||||
})
|
||||
|
||||
test("merges a singleton into an overlapping cluster at a distant zoom", () => {
|
||||
const points = [
|
||||
{id: "cluster", type: "cluster", count: 351, longitude: 0, latitude: 0},
|
||||
{id: "request", type: "request", longitude: 28, latitude: 0, exact: true}
|
||||
]
|
||||
|
||||
const result = mergeNearbyMapPoints(flatMap(1), points)
|
||||
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].type, "cluster")
|
||||
assert.equal(result[0].count, 352)
|
||||
assert.deepEqual(result[0].bounds, {west: 0, south: 0, east: 28, north: 0})
|
||||
})
|
||||
|
||||
test("keeps separated singleton points interactive at a closer zoom", () => {
|
||||
const points = [
|
||||
{id: "left", type: "request", longitude: 0, latitude: 0, exact: true},
|
||||
{
|
||||
id: "right",
|
||||
type: "request",
|
||||
longitude: 30,
|
||||
latitude: 0,
|
||||
radius_meters: 1_000
|
||||
}
|
||||
]
|
||||
|
||||
const result = mergeNearbyMapPoints(flatMap(3), points)
|
||||
|
||||
assert.deepEqual(result, points)
|
||||
})
|
||||
|
||||
test("uses larger non-overlapping markers for larger totals", () => {
|
||||
assert.equal(clusterDiameterPixels(999), 44)
|
||||
assert.equal(clusterDiameterPixels(1_000), 52)
|
||||
assert.equal(clusterDiameterPixels(10_000), 56)
|
||||
assert.equal(clusterDiameterPixels(100_000), 60)
|
||||
assert.equal(clusterSizeClass(999), "is-md")
|
||||
assert.equal(clusterSizeClass(1_000), "is-lg")
|
||||
assert.equal(clusterSizeClass(10_000), "is-xl")
|
||||
assert.equal(clusterSizeClass(100_000), "is-xxl")
|
||||
})
|
||||
|
|
@ -11,7 +11,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
@default_rows 1_000_000
|
||||
@synthetic_users 1_000
|
||||
@viewer_email "scale-viewer@example.invalid"
|
||||
@fixture_version 7
|
||||
@fixture_version 8
|
||||
@request_open_modulus 1
|
||||
@activity_open_modulus 1
|
||||
@places [
|
||||
|
|
@ -425,10 +425,11 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
'Synthetic data only; no real-world action is requested.',
|
||||
jsonb_build_object(
|
||||
'scale_fixture', true,
|
||||
'scale_fixture_version', $7::integer
|
||||
'scale_fixture_version', $7::integer,
|
||||
'scale_place', place.name,
|
||||
'scale_location_index', local.local_index
|
||||
),
|
||||
place.name || ' synthetic area ' ||
|
||||
((variation.category_key + variation.time_key) % 100 + 1),
|
||||
place.name || ' synthetic location ' || (local.local_index + 1),
|
||||
CASE WHEN variation.visibility_key % 20 = 0 THEN NULL
|
||||
ELSE ST_SetSRID(
|
||||
ST_MakePoint(coordinates.longitude, coordinates.latitude),
|
||||
|
|
@ -469,8 +470,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
FROM generate_series(1, $1) AS value
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
md5('wnh-scale-request-v5-' || value) AS fixture_hash,
|
||||
md5('wnh-scale-request-geography-v5-' || value) AS geography_hash
|
||||
md5('wnh-scale-request-v8-' || value) AS fixture_hash
|
||||
) AS fixture
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
|
|
@ -482,52 +482,56 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
AS time_key,
|
||||
('x' || substr(fixture.fixture_hash, 25, 8))::bit(32)::bigint
|
||||
AS category_key,
|
||||
('x' || substr(fixture.geography_hash, 1, 8))::bit(32)::bigint
|
||||
AS place_key,
|
||||
('x' || substr(fixture.geography_hash, 9, 8))::bit(32)::bigint
|
||||
AS angle_key,
|
||||
('x' || substr(fixture.geography_hash, 17, 8))::bit(32)::bigint
|
||||
AS distance_key,
|
||||
('x' || substr(fixture.geography_hash, 25, 8))::bit(32)::bigint
|
||||
AS owner_key
|
||||
('x' || substr(md5('wnh-scale-request-owner-v8-' || value), 1, 8))
|
||||
::bit(32)::bigint AS owner_key
|
||||
) AS variation
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT name, longitude, latitude
|
||||
SELECT name, longitude, latitude, position
|
||||
FROM unnest(
|
||||
$4::text[],
|
||||
$5::double precision[],
|
||||
$6::double precision[]
|
||||
) WITH ORDINALITY AS places(name, longitude, latitude, position)
|
||||
WHERE places.position = variation.place_key % cardinality($4::text[]) + 1
|
||||
WHERE places.position = ((value - 1) % cardinality($4::text[])) + 1
|
||||
) AS place
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
2 * pi() *
|
||||
((variation.angle_key % 1000003)::double precision / 1000003.0) AS angle,
|
||||
sqrt(
|
||||
((variation.distance_key / 20 % 1000003)::double precision + 1) /
|
||||
1000004.0
|
||||
) *
|
||||
CASE WHEN variation.distance_key % 20 < 12 THEN 15.0
|
||||
WHEN variation.distance_key % 20 < 18 THEN 60.0
|
||||
ELSE 220.0
|
||||
END AS distance_km
|
||||
) AS geography
|
||||
(value - 1) / cardinality($4::text[]) AS local_index
|
||||
) AS local
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
place.latitude +
|
||||
geography.distance_km / 111.32 * sin(geography.angle) AS raw_latitude,
|
||||
(
|
||||
((local.local_index % 256) * 73 +
|
||||
((local.local_index / 256) % 256) * 16) % 256
|
||||
)::double precision AS grid_x,
|
||||
(
|
||||
(((local.local_index / 256) % 256) * 53 +
|
||||
(local.local_index % 256) * 29) % 256
|
||||
)::double precision AS grid_y
|
||||
) AS grid
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
(grid.grid_x / 255.0 - 0.5) * 12.0 AS east_km,
|
||||
(grid.grid_y / 255.0 - 0.5) * 12.0 AS north_km,
|
||||
radians((place.position * 137.50776405) % 360.0) AS rotation
|
||||
) AS local_offset
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
local_offset.east_km * cos(local_offset.rotation) -
|
||||
local_offset.north_km * sin(local_offset.rotation) AS east_km,
|
||||
local_offset.east_km * sin(local_offset.rotation) +
|
||||
local_offset.north_km * cos(local_offset.rotation) AS north_km
|
||||
) AS rotated_offset
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
greatest(
|
||||
-84.0,
|
||||
least(84.0, place.latitude + rotated_offset.north_km / 111.32)
|
||||
) AS latitude,
|
||||
place.longitude +
|
||||
geography.distance_km /
|
||||
(111.32 * greatest(abs(cos(radians(place.latitude))), 0.2)) *
|
||||
cos(geography.angle) AS raw_longitude
|
||||
) AS raw_coordinates
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
greatest(-84.0, least(84.0, raw_coordinates.raw_latitude)) AS latitude,
|
||||
(raw_coordinates.raw_longitude + 180.0) -
|
||||
360.0 * floor((raw_coordinates.raw_longitude + 180.0) / 360.0) -
|
||||
180.0 AS longitude
|
||||
rotated_offset.east_km /
|
||||
(111.32 * greatest(abs(cos(radians(place.latitude))), 0.2))
|
||||
AS longitude
|
||||
) AS coordinates
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
|
|
@ -578,10 +582,11 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
'Synthetic scale activity used to test geographic discovery, privacy, and UI.',
|
||||
jsonb_build_object(
|
||||
'scale_fixture', true,
|
||||
'scale_fixture_version', $7::integer
|
||||
'scale_fixture_version', $7::integer,
|
||||
'scale_place', place.name,
|
||||
'scale_location_index', local.local_index
|
||||
),
|
||||
place.name || ' synthetic area ' ||
|
||||
((variation.category_key + variation.time_key) % 100 + 1),
|
||||
place.name || ' synthetic location ' || (local.local_index + 1),
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(coordinates.longitude, coordinates.latitude),
|
||||
4326
|
||||
|
|
@ -615,8 +620,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
FROM generate_series(1, $1) AS value
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
md5('wnh-scale-activity-v5-' || value) AS fixture_hash,
|
||||
md5('wnh-scale-activity-geography-v5-' || value) AS geography_hash
|
||||
md5('wnh-scale-activity-v8-' || value) AS fixture_hash
|
||||
) AS fixture
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
|
|
@ -628,52 +632,56 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
AS time_key,
|
||||
('x' || substr(fixture.fixture_hash, 25, 8))::bit(32)::bigint
|
||||
AS category_key,
|
||||
('x' || substr(fixture.geography_hash, 1, 8))::bit(32)::bigint
|
||||
AS place_key,
|
||||
('x' || substr(fixture.geography_hash, 9, 8))::bit(32)::bigint
|
||||
AS angle_key,
|
||||
('x' || substr(fixture.geography_hash, 17, 8))::bit(32)::bigint
|
||||
AS distance_key,
|
||||
('x' || substr(fixture.geography_hash, 25, 8))::bit(32)::bigint
|
||||
AS owner_key
|
||||
('x' || substr(md5('wnh-scale-activity-owner-v8-' || value), 1, 8))
|
||||
::bit(32)::bigint AS owner_key
|
||||
) AS variation
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT name, longitude, latitude
|
||||
SELECT name, longitude, latitude, position
|
||||
FROM unnest(
|
||||
$4::text[],
|
||||
$5::double precision[],
|
||||
$6::double precision[]
|
||||
) WITH ORDINALITY AS places(name, longitude, latitude, position)
|
||||
WHERE places.position = variation.place_key % cardinality($4::text[]) + 1
|
||||
WHERE places.position = ((value - 1) % cardinality($4::text[])) + 1
|
||||
) AS place
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
2 * pi() *
|
||||
((variation.angle_key % 1000003)::double precision / 1000003.0) AS angle,
|
||||
sqrt(
|
||||
((variation.distance_key / 20 % 1000003)::double precision + 1) /
|
||||
1000004.0
|
||||
) *
|
||||
CASE WHEN variation.distance_key % 20 < 12 THEN 15.0
|
||||
WHEN variation.distance_key % 20 < 18 THEN 60.0
|
||||
ELSE 220.0
|
||||
END AS distance_km
|
||||
) AS geography
|
||||
(value - 1) / cardinality($4::text[]) AS local_index
|
||||
) AS local
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
place.latitude +
|
||||
geography.distance_km / 111.32 * sin(geography.angle) AS raw_latitude,
|
||||
(
|
||||
((local.local_index % 256) * 73 +
|
||||
((local.local_index / 256) % 256) * 16 + 31) % 256
|
||||
)::double precision AS grid_x,
|
||||
(
|
||||
(((local.local_index / 256) % 256) * 53 +
|
||||
(local.local_index % 256) * 29 + 47) % 256
|
||||
)::double precision AS grid_y
|
||||
) AS grid
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
(grid.grid_x / 255.0 - 0.5) * 12.0 AS east_km,
|
||||
(grid.grid_y / 255.0 - 0.5) * 12.0 AS north_km,
|
||||
radians((place.position * 137.50776405 + 19.0) % 360.0) AS rotation
|
||||
) AS local_offset
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
local_offset.east_km * cos(local_offset.rotation) -
|
||||
local_offset.north_km * sin(local_offset.rotation) AS east_km,
|
||||
local_offset.east_km * sin(local_offset.rotation) +
|
||||
local_offset.north_km * cos(local_offset.rotation) AS north_km
|
||||
) AS rotated_offset
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
greatest(
|
||||
-84.0,
|
||||
least(84.0, place.latitude + rotated_offset.north_km / 111.32)
|
||||
) AS latitude,
|
||||
place.longitude +
|
||||
geography.distance_km /
|
||||
(111.32 * greatest(abs(cos(radians(place.latitude))), 0.2)) *
|
||||
cos(geography.angle) AS raw_longitude
|
||||
) AS raw_coordinates
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT
|
||||
greatest(-84.0, least(84.0, raw_coordinates.raw_latitude)) AS latitude,
|
||||
(raw_coordinates.raw_longitude + 180.0) -
|
||||
360.0 * floor((raw_coordinates.raw_longitude + 180.0) / 360.0) -
|
||||
180.0 AS longitude
|
||||
rotated_offset.east_km /
|
||||
(111.32 * greatest(abs(cos(radians(place.latitude))), 0.2))
|
||||
AS longitude
|
||||
) AS coordinates
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
|
|
@ -1125,7 +1133,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
|
||||
defp fixture_place_count!("help_requests") do
|
||||
scalar!("""
|
||||
SELECT count(DISTINCT regexp_replace(location_label, ' synthetic area [0-9]+$', ''))
|
||||
SELECT count(DISTINCT regexp_replace(location_label, ' synthetic location [0-9]+$', ''))
|
||||
FROM help_requests
|
||||
WHERE structured_data @> '{"scale_fixture": true}'::jsonb
|
||||
""")
|
||||
|
|
@ -1133,7 +1141,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
|
||||
defp fixture_place_count!("activities") do
|
||||
scalar!("""
|
||||
SELECT count(DISTINCT regexp_replace(location_label, ' synthetic area [0-9]+$', ''))
|
||||
SELECT count(DISTINCT regexp_replace(location_label, ' synthetic location [0-9]+$', ''))
|
||||
FROM activities
|
||||
WHERE structured_data @> '{"scale_fixture": true}'::jsonb
|
||||
""")
|
||||
|
|
@ -1146,7 +1154,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
SELECT count(*) AS place_rows
|
||||
FROM help_requests
|
||||
WHERE structured_data @> '{"scale_fixture": true}'::jsonb
|
||||
GROUP BY regexp_replace(location_label, ' synthetic area [0-9]+$', '')
|
||||
GROUP BY regexp_replace(location_label, ' synthetic location [0-9]+$', '')
|
||||
) AS grouped_places
|
||||
""")
|
||||
end
|
||||
|
|
@ -1158,7 +1166,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
SELECT count(*) AS place_rows
|
||||
FROM activities
|
||||
WHERE structured_data @> '{"scale_fixture": true}'::jsonb
|
||||
GROUP BY regexp_replace(location_label, ' synthetic area [0-9]+$', '')
|
||||
GROUP BY regexp_replace(location_label, ' synthetic location [0-9]+$', '')
|
||||
) AS grouped_places
|
||||
""")
|
||||
end
|
||||
|
|
@ -1173,7 +1181,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
AND status = 'open'
|
||||
AND expires_at > now()
|
||||
AND location_visibility <> 'hidden'
|
||||
GROUP BY regexp_replace(location_label, ' synthetic area [0-9]+$', '')
|
||||
GROUP BY regexp_replace(location_label, ' synthetic location [0-9]+$', '')
|
||||
) AS grouped_places
|
||||
""")
|
||||
end
|
||||
|
|
@ -1188,7 +1196,7 @@ defmodule Mix.Tasks.Wnh.ScaleFixtures do
|
|||
AND status = 'open'
|
||||
AND starts_at > now()
|
||||
AND location_visibility = 'approximate_public'
|
||||
GROUP BY regexp_replace(location_label, ' synthetic area [0-9]+$', '')
|
||||
GROUP BY regexp_replace(location_label, ' synthetic location [0-9]+$', '')
|
||||
) AS grouped_places
|
||||
""")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule WhoNeedHelp.Activities do
|
|||
alias WhoNeedHelp.Activities.{Activity, Message, Participant}
|
||||
alias WhoNeedHelp.Catalog
|
||||
alias WhoNeedHelp.Catalog.Category
|
||||
alias WhoNeedHelp.Help.DiscoveryViewport
|
||||
alias WhoNeedHelp.Help.{DiscoveryCluster, DiscoveryViewport}
|
||||
alias WhoNeedHelp.Pagination
|
||||
alias WhoNeedHelp.Repo
|
||||
alias WhoNeedHelp.Trust
|
||||
|
|
@ -53,7 +53,9 @@ defmodule WhoNeedHelp.Activities do
|
|||
end
|
||||
|
||||
def map_discovery_items(%Scope{user: user}, filters, %DiscoveryViewport{} = viewport) do
|
||||
cell_size = DiscoveryViewport.cluster_cell_meters(viewport)
|
||||
level = DiscoveryCluster.level_for_zoom(viewport.zoom)
|
||||
cell_size = DiscoveryCluster.cell_size_meters(level)
|
||||
world_half = DiscoveryCluster.world_half_meters()
|
||||
|
||||
points =
|
||||
user
|
||||
|
|
@ -87,65 +89,75 @@ defmodule WhoNeedHelp.Activities do
|
|||
|
||||
points
|
||||
|> subquery()
|
||||
|> group_by(
|
||||
[point],
|
||||
|> group_by([point], [
|
||||
fragment(
|
||||
"ST_SnapToGrid(ST_Transform(?, 3857), ?)",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
fragment(
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
)
|
||||
])
|
||||
|> select([point], %{
|
||||
count: count(point.id),
|
||||
activity_id: fragment("CASE WHEN count(*) = 1 THEN min(?::text) END", point.id),
|
||||
title: fragment("CASE WHEN count(*) = 1 THEN min(?) END", point.title),
|
||||
location_label: fragment("CASE WHEN count(*) = 1 THEN min(?) END", point.location_label),
|
||||
longitude:
|
||||
fragment(
|
||||
"CASE WHEN count(*) = 1 THEN min(ST_X(?)) ELSE avg(ST_X(?)) END",
|
||||
point.public_location,
|
||||
point.public_location
|
||||
),
|
||||
latitude:
|
||||
fragment(
|
||||
"CASE WHEN count(*) = 1 THEN min(ST_Y(?)) ELSE avg(ST_Y(?)) END",
|
||||
point.public_location,
|
||||
point.public_location
|
||||
),
|
||||
longitude: fragment("avg(ST_X(?))", point.public_location),
|
||||
latitude: fragment("avg(ST_Y(?))", point.public_location),
|
||||
west: fragment("min(ST_X(?))", point.public_location),
|
||||
south: fragment("min(ST_Y(?))", point.public_location),
|
||||
east: fragment("max(ST_X(?))", point.public_location),
|
||||
north: fragment("max(ST_Y(?))", point.public_location),
|
||||
west_m: fragment("min(ST_X(ST_Transform(?, 3857)))", point.public_location),
|
||||
south_m: fragment("min(ST_Y(ST_Transform(?, 3857)))", point.public_location),
|
||||
east_m: fragment("max(ST_X(ST_Transform(?, 3857)))", point.public_location),
|
||||
north_m: fragment("max(ST_Y(ST_Transform(?, 3857)))", point.public_location),
|
||||
cell_x:
|
||||
fragment(
|
||||
"ST_X(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
cell_y:
|
||||
fragment(
|
||||
"ST_Y(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
})
|
||||
|> order_by(
|
||||
[point],
|
||||
|> order_by([point],
|
||||
asc:
|
||||
fragment(
|
||||
"ST_X(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
asc:
|
||||
fragment(
|
||||
"ST_Y(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.map(&map_discovery_item(&1, viewport.zoom))
|
||||
|> Enum.map(&map_discovery_item(&1, level))
|
||||
end
|
||||
|
||||
def count_map_discovery_items(%Scope{user: user}, filters) do
|
||||
user
|
||||
|> open_activities_query(filters, nil)
|
||||
|> where([activity], activity.location_visibility == :approximate_public)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
def list_my_activities(%Scope{user: user}) do
|
||||
|
|
@ -968,10 +980,12 @@ defmodule WhoNeedHelp.Activities do
|
|||
defp longitude_in_viewport?(longitude, %DiscoveryViewport{west: west, east: east}),
|
||||
do: longitude >= west or longitude <= east
|
||||
|
||||
defp map_discovery_item(%{count: 1} = row, _zoom) do
|
||||
defp map_discovery_item(%{count: 1} = row, level) do
|
||||
%{
|
||||
type: "activity",
|
||||
id: row.activity_id,
|
||||
parent_cluster_id: DiscoveryCluster.parent_id(:activity, level, row.cell_x, row.cell_y),
|
||||
hierarchy_level: level,
|
||||
title: row.title,
|
||||
location: row.location_label,
|
||||
latitude: row.latitude,
|
||||
|
|
@ -980,15 +994,24 @@ defmodule WhoNeedHelp.Activities do
|
|||
}
|
||||
end
|
||||
|
||||
defp map_discovery_item(row, zoom) do
|
||||
digest =
|
||||
:crypto.hash(:sha256, "activity:#{zoom}:#{row.cell_x}:#{row.cell_y}")
|
||||
|> Base.url_encode64(padding: false)
|
||||
|> binary_part(0, 16)
|
||||
defp map_discovery_item(row, level) do
|
||||
cluster_id = DiscoveryCluster.cluster_id(:activity, level, row.cell_x, row.cell_y)
|
||||
|
||||
%{
|
||||
type: "cluster",
|
||||
id: "activity-cluster-#{digest}",
|
||||
id: cluster_id,
|
||||
cluster_id: cluster_id,
|
||||
parent_cluster_id: DiscoveryCluster.parent_id(:activity, level, row.cell_x, row.cell_y),
|
||||
hierarchy_level: level,
|
||||
point_count: row.count,
|
||||
expansion_zoom:
|
||||
DiscoveryCluster.expansion_zoom(
|
||||
level,
|
||||
row.west_m,
|
||||
row.south_m,
|
||||
row.east_m,
|
||||
row.north_m
|
||||
),
|
||||
count: row.count,
|
||||
latitude: row.latitude,
|
||||
longitude: row.longitude,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule WhoNeedHelp.Help do
|
|||
alias WhoNeedHelp.Accounts.Scope
|
||||
alias WhoNeedHelp.Catalog
|
||||
alias WhoNeedHelp.Catalog.Category
|
||||
alias WhoNeedHelp.Help.{Assignment, DiscoveryViewport, HelpRequest}
|
||||
alias WhoNeedHelp.Help.{Assignment, DiscoveryCluster, DiscoveryViewport, HelpRequest}
|
||||
alias WhoNeedHelp.Pagination
|
||||
alias WhoNeedHelp.ProductAnalytics
|
||||
alias WhoNeedHelp.Push
|
||||
|
|
@ -64,12 +64,17 @@ defmodule WhoNeedHelp.Help do
|
|||
end
|
||||
|
||||
def map_discovery_items(%Scope{user: user}, filters, %DiscoveryViewport{} = viewport) do
|
||||
cell_size = DiscoveryViewport.cluster_cell_meters(viewport)
|
||||
level = DiscoveryCluster.level_for_zoom(viewport.zoom)
|
||||
cell_size = DiscoveryCluster.cell_size_meters(level)
|
||||
world_half = DiscoveryCluster.world_half_meters()
|
||||
|
||||
points =
|
||||
user
|
||||
|> open_requests_query(filters, nil)
|
||||
|> where([request], not is_nil(request.location))
|
||||
|> where(
|
||||
[request],
|
||||
not is_nil(request.location) and request.location_visibility != :hidden
|
||||
)
|
||||
|> filter_request_map_viewport(viewport)
|
||||
|> select([request], %{
|
||||
id: request.id,
|
||||
|
|
@ -105,14 +110,20 @@ defmodule WhoNeedHelp.Help do
|
|||
|
||||
points
|
||||
|> subquery()
|
||||
|> group_by(
|
||||
[point],
|
||||
|> group_by([point], [
|
||||
fragment(
|
||||
"ST_SnapToGrid(ST_Transform(?, 3857), ?)",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
fragment(
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
)
|
||||
])
|
||||
|> select([point], %{
|
||||
count: count(point.id),
|
||||
urgent_count: fragment("count(*) FILTER (WHERE ? = 'now')::bigint", point.urgency),
|
||||
|
|
@ -125,52 +136,59 @@ defmodule WhoNeedHelp.Help do
|
|||
point.location_visibility
|
||||
),
|
||||
radius_meters: fragment("CASE WHEN count(*) = 1 THEN max(?) END", point.radius_meters),
|
||||
longitude:
|
||||
fragment(
|
||||
"CASE WHEN count(*) = 1 THEN min(ST_X(?)) ELSE avg(ST_X(?)) END",
|
||||
point.public_location,
|
||||
point.public_location
|
||||
),
|
||||
latitude:
|
||||
fragment(
|
||||
"CASE WHEN count(*) = 1 THEN min(ST_Y(?)) ELSE avg(ST_Y(?)) END",
|
||||
point.public_location,
|
||||
point.public_location
|
||||
),
|
||||
longitude: fragment("avg(ST_X(?))", point.public_location),
|
||||
latitude: fragment("avg(ST_Y(?))", point.public_location),
|
||||
west: fragment("min(ST_X(?))", point.public_location),
|
||||
south: fragment("min(ST_Y(?))", point.public_location),
|
||||
east: fragment("max(ST_X(?))", point.public_location),
|
||||
north: fragment("max(ST_Y(?))", point.public_location),
|
||||
west_m: fragment("min(ST_X(ST_Transform(?, 3857)))", point.public_location),
|
||||
south_m: fragment("min(ST_Y(ST_Transform(?, 3857)))", point.public_location),
|
||||
east_m: fragment("max(ST_X(ST_Transform(?, 3857)))", point.public_location),
|
||||
north_m: fragment("max(ST_Y(ST_Transform(?, 3857)))", point.public_location),
|
||||
cell_x:
|
||||
fragment(
|
||||
"ST_X(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
cell_y:
|
||||
fragment(
|
||||
"ST_Y(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
})
|
||||
|> order_by(
|
||||
[point],
|
||||
|> order_by([point],
|
||||
asc:
|
||||
fragment(
|
||||
"ST_X(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_X(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
),
|
||||
asc:
|
||||
fragment(
|
||||
"ST_Y(ST_SnapToGrid(ST_Transform(?, 3857), ?))",
|
||||
"floor((ST_Y(ST_Transform(?, 3857)) + ?) / ?)::bigint",
|
||||
point.public_location,
|
||||
constant(^world_half),
|
||||
constant(^cell_size)
|
||||
)
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.map(&map_discovery_item(&1, viewport.zoom))
|
||||
|> Enum.map(&map_discovery_item(&1, level))
|
||||
end
|
||||
|
||||
def count_map_discovery_items(%Scope{user: user}, filters) do
|
||||
user
|
||||
|> open_requests_query(filters, nil)
|
||||
|> where(
|
||||
[request],
|
||||
not is_nil(request.location) and request.location_visibility != :hidden
|
||||
)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
# Discovery lists include a privacy area's intersection with the viewport.
|
||||
|
|
@ -1274,10 +1292,12 @@ defmodule WhoNeedHelp.Help do
|
|||
where(query, ^condition)
|
||||
end
|
||||
|
||||
defp map_discovery_item(%{count: 1} = row, _zoom) do
|
||||
defp map_discovery_item(%{count: 1} = row, level) do
|
||||
%{
|
||||
type: "request",
|
||||
id: row.request_id,
|
||||
parent_cluster_id: DiscoveryCluster.parent_id(:request, level, row.cell_x, row.cell_y),
|
||||
hierarchy_level: level,
|
||||
title: row.title,
|
||||
location: row.location_label,
|
||||
latitude: row.latitude,
|
||||
|
|
@ -1287,15 +1307,24 @@ defmodule WhoNeedHelp.Help do
|
|||
}
|
||||
end
|
||||
|
||||
defp map_discovery_item(row, zoom) do
|
||||
digest =
|
||||
:crypto.hash(:sha256, "#{zoom}:#{row.cell_x}:#{row.cell_y}")
|
||||
|> Base.url_encode64(padding: false)
|
||||
|> binary_part(0, 16)
|
||||
defp map_discovery_item(row, level) do
|
||||
cluster_id = DiscoveryCluster.cluster_id(:request, level, row.cell_x, row.cell_y)
|
||||
|
||||
%{
|
||||
type: "cluster",
|
||||
id: "cluster-#{digest}",
|
||||
id: cluster_id,
|
||||
cluster_id: cluster_id,
|
||||
parent_cluster_id: DiscoveryCluster.parent_id(:request, level, row.cell_x, row.cell_y),
|
||||
hierarchy_level: level,
|
||||
point_count: row.count,
|
||||
expansion_zoom:
|
||||
DiscoveryCluster.expansion_zoom(
|
||||
level,
|
||||
row.west_m,
|
||||
row.south_m,
|
||||
row.east_m,
|
||||
row.north_m
|
||||
),
|
||||
count: row.count,
|
||||
urgent_count: row.urgent_count,
|
||||
latitude: row.latitude,
|
||||
|
|
|
|||
99
lib/who_need_help/help/discovery_cluster.ex
Normal file
99
lib/who_need_help/help/discovery_cluster.ex
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
defmodule WhoNeedHelp.Help.DiscoveryCluster do
|
||||
@moduledoc """
|
||||
Stable Web Mercator hierarchy used as the server-side input to MapLibre
|
||||
Supercluster.
|
||||
|
||||
A browser cannot safely receive every discovery point once the data set
|
||||
reaches millions of rows. The server therefore groups points into a fixed
|
||||
quadtree. Unlike viewport-sized `ST_SnapToGrid` buckets, every cell has a
|
||||
stable identity and every cell at level `n + 1` belongs to exactly one parent
|
||||
at level `n`.
|
||||
|
||||
MapLibre receives these weighted nodes and applies its native Supercluster
|
||||
implementation for pixel-perfect collision handling. When a server node is
|
||||
reached, `expansion_zoom/5` identifies the first map zoom where that node has
|
||||
more than one child cell.
|
||||
"""
|
||||
|
||||
@world_half_meters 20_037_508.342_789_244
|
||||
@world_width_meters @world_half_meters * 2.0
|
||||
@cluster_level_offset 2
|
||||
@minimum_level 0
|
||||
@maximum_map_zoom 22
|
||||
@maximum_level @maximum_map_zoom + @cluster_level_offset
|
||||
|
||||
@type kind :: :request | :activity
|
||||
|
||||
def level_for_zoom(zoom) when is_number(zoom) do
|
||||
zoom
|
||||
|> floor()
|
||||
|> Kernel.+(@cluster_level_offset)
|
||||
|> clamp(@minimum_level, @maximum_level)
|
||||
end
|
||||
|
||||
def cell_size_meters(level) when is_integer(level) and level in 0..@maximum_level do
|
||||
@world_width_meters / :math.pow(2.0, level)
|
||||
end
|
||||
|
||||
def world_half_meters, do: @world_half_meters
|
||||
|
||||
def cluster_id(kind, level, cell_x, cell_y)
|
||||
when kind in [:request, :activity] and is_integer(level) and is_integer(cell_x) and
|
||||
is_integer(cell_y) do
|
||||
"#{kind}:#{level}:#{cell_x}:#{cell_y}"
|
||||
end
|
||||
|
||||
def parent_id(_kind, 0, _cell_x, _cell_y), do: nil
|
||||
|
||||
def parent_id(kind, level, cell_x, cell_y)
|
||||
when kind in [:request, :activity] and is_integer(level) and level > 0 and
|
||||
is_integer(cell_x) and is_integer(cell_y) do
|
||||
cluster_id(kind, level - 1, div(cell_x, 2), div(cell_y, 2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first map zoom at which a fixed hierarchy cell separates.
|
||||
|
||||
The bounds must be expressed in EPSG:3857 meters and describe the actual
|
||||
member points, not the cell envelope. For coincident points the result is the
|
||||
maximum supported map zoom.
|
||||
"""
|
||||
def expansion_zoom(level, west, south, east, north)
|
||||
when is_integer(level) and is_number(west) and is_number(south) and is_number(east) and
|
||||
is_number(north) do
|
||||
west = clamp_coordinate(west)
|
||||
east = clamp_coordinate(east)
|
||||
south = clamp_coordinate(south)
|
||||
north = clamp_coordinate(north)
|
||||
|
||||
candidate_level =
|
||||
if level >= @maximum_level do
|
||||
@maximum_level
|
||||
else
|
||||
Enum.find((level + 1)..@maximum_level, @maximum_level, fn candidate ->
|
||||
cell_index(west, candidate) != cell_index(east, candidate) or
|
||||
cell_index(south, candidate) != cell_index(north, candidate)
|
||||
end)
|
||||
end
|
||||
|
||||
candidate_level
|
||||
|> Kernel.-(@cluster_level_offset)
|
||||
|> clamp(0, @maximum_map_zoom)
|
||||
end
|
||||
|
||||
defp cell_index(coordinate, level) do
|
||||
coordinate
|
||||
|> Kernel.+(@world_half_meters)
|
||||
|> Kernel./(cell_size_meters(level))
|
||||
|> floor()
|
||||
end
|
||||
|
||||
defp clamp_coordinate(value) do
|
||||
value
|
||||
|> Kernel.*(1.0)
|
||||
|> max(-@world_half_meters)
|
||||
|> min(@world_half_meters - 1.0e-7)
|
||||
end
|
||||
|
||||
defp clamp(value, minimum, maximum), do: value |> max(minimum) |> min(maximum)
|
||||
end
|
||||
|
|
@ -2,18 +2,18 @@ defmodule WhoNeedHelp.Help.DiscoveryViewport do
|
|||
@moduledoc """
|
||||
Validated map bounds used by request discovery.
|
||||
|
||||
The viewport carries its rendered pixel size so server-side clustering can
|
||||
match the amount of detail the browser can actually display. Longitudes
|
||||
crossing the antimeridian are represented as two PostGIS envelopes.
|
||||
The viewport carries its rendered pixel size for subscription capacity and
|
||||
input validation. Discovery clustering itself uses a stable zoom-indexed
|
||||
hierarchy, so a cluster does not change identity when the canvas is resized.
|
||||
Longitudes crossing the antimeridian are represented as two PostGIS
|
||||
envelopes.
|
||||
"""
|
||||
|
||||
@max_mercator_latitude 85.051_128_78
|
||||
@maximum_canvas_dimension 4_096
|
||||
@minimum_canvas_dimension 160
|
||||
@cluster_cell_pixels 64.0
|
||||
@map_tile_pixels 256
|
||||
@maximum_subscription_zoom 12
|
||||
@earth_radius_meters 6_378_137.0
|
||||
|
||||
@enforce_keys [:west, :south, :east, :north, :zoom, :width, :height]
|
||||
defstruct [:west, :south, :east, :north, :zoom, :width, :height]
|
||||
|
|
@ -106,22 +106,6 @@ defmodule WhoNeedHelp.Help.DiscoveryViewport do
|
|||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
def cluster_cell_meters(%__MODULE__{} = viewport) do
|
||||
horizontal_cells = max(viewport.width / @cluster_cell_pixels, 1.0)
|
||||
vertical_cells = max(viewport.height / @cluster_cell_pixels, 1.0)
|
||||
|
||||
width_meters =
|
||||
viewport
|
||||
|> envelopes()
|
||||
|> Enum.map(fn {west, _south, east, _north} -> mercator_x(east) - mercator_x(west) end)
|
||||
|> Enum.sum()
|
||||
|
||||
height_meters = mercator_y(viewport.north) - mercator_y(viewport.south)
|
||||
|
||||
max(width_meters / horizontal_cells, height_meters / vertical_cells)
|
||||
|> max(0.01)
|
||||
end
|
||||
|
||||
def query_params(%__MODULE__{} = viewport) do
|
||||
%{
|
||||
"west" => format_number(viewport.west),
|
||||
|
|
@ -310,14 +294,6 @@ defmodule WhoNeedHelp.Help.DiscoveryViewport do
|
|||
defp latitude?(value),
|
||||
do: value >= -@max_mercator_latitude and value <= @max_mercator_latitude
|
||||
|
||||
defp mercator_x(longitude), do: @earth_radius_meters * degrees_to_radians(longitude)
|
||||
|
||||
defp mercator_y(latitude) do
|
||||
latitude = min(max(latitude, -@max_mercator_latitude), @max_mercator_latitude)
|
||||
radians = degrees_to_radians(latitude)
|
||||
@earth_radius_meters * :math.log(:math.tan(:math.pi() / 4 + radians / 2))
|
||||
end
|
||||
|
||||
defp degrees_to_radians(value), do: value * :math.pi() / 180.0
|
||||
|
||||
defp clamp(value, minimum, maximum), do: min(max(value, minimum), maximum)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
|> assign(:activities_cursor, nil)
|
||||
|> assign(:map_items, Jason.encode!([]))
|
||||
|> assign(:map_result_count, 0)
|
||||
|> assign(:map_total_count, 0)
|
||||
|> assign(:discovery_loading, true)
|
||||
|> assign(:refresh_timer, nil)
|
||||
|> assign(:my_activities, [])
|
||||
|
|
@ -266,6 +267,15 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
|
||||
activities = activities_page.entries
|
||||
|
||||
map_total_count =
|
||||
if discovery_mode == "nearby" and view_mode != "list" do
|
||||
DiscoveryQueryCache.fetch(map_total_cache_key(scope, filters), fn ->
|
||||
Activities.count_map_discovery_items(scope, filters)
|
||||
end)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
%{
|
||||
discovery_loading: false,
|
||||
activities: activities,
|
||||
|
|
@ -273,7 +283,8 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
my_activities: my_activities_page.entries,
|
||||
my_activities_cursor: my_activities_page.next_cursor,
|
||||
map_items: Jason.encode!(map_items),
|
||||
map_result_count: Enum.reduce(map_items, 0, &(map_item_count(&1) + &2))
|
||||
map_result_count: Enum.reduce(map_items, 0, &(map_item_count(&1) + &2)),
|
||||
map_total_count: map_total_count
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -338,6 +349,11 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
)
|
||||
)
|
||||
|
||||
:ok =
|
||||
DiscoveryQueryCache.invalidate(
|
||||
map_total_cache_key(socket.assigns.current_scope, socket.assigns.filters)
|
||||
)
|
||||
|
||||
socket
|
||||
end
|
||||
|
||||
|
|
@ -345,6 +361,8 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
{:activities, scope.user.id, filters, discovery_mode, view_mode, viewport}
|
||||
end
|
||||
|
||||
defp map_total_cache_key(scope, filters), do: {:activity_map_total, scope.user.id, filters}
|
||||
|
||||
defp empty_filters, do: %{"category_id" => "", "area" => ""}
|
||||
|
||||
defp normalize_filters(params) do
|
||||
|
|
@ -513,6 +531,11 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
count: @map_result_count
|
||||
)}
|
||||
</strong>
|
||||
<span class="ml-1 text-sm text-base-content/60">
|
||||
{gettext("of %{count} matching activities with map locations",
|
||||
count: @map_total_count
|
||||
)}
|
||||
</span>
|
||||
<p class="text-sm text-base-content/60">
|
||||
{gettext("Move the map, then search the newly visible area.")}
|
||||
</p>
|
||||
|
|
@ -649,6 +672,9 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
|
|||
data-item-path-prefix="/activities"
|
||||
data-item-label={gettext("Open activity")}
|
||||
data-cluster-label={gettext("%{count} activities", count: "%{count}")}
|
||||
data-cluster-max-zoom-label={
|
||||
gettext("Maximum zoom reached. Matching activities are listed beside the map.")
|
||||
}
|
||||
data-map-unavailable-label={
|
||||
gettext("The map is unavailable in this browser. Activity details remain usable.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
|> assign(:reputation, @empty_reputation)
|
||||
|> assign(:map_items, Jason.encode!([]))
|
||||
|> assign(:map_result_count, 0)
|
||||
|> assign(:map_total_count, 0)
|
||||
|> assign(:discovery_loading, true)
|
||||
|> assign(:refresh_timer, nil)
|
||||
|> assign(:page_title, gettext("Nearby help"))
|
||||
|
|
@ -259,6 +260,15 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
%Pagination.Page{}
|
||||
end
|
||||
|
||||
map_total_count =
|
||||
if discovery_mode == "nearby" and view_mode != "list" do
|
||||
DiscoveryQueryCache.fetch(map_total_cache_key(scope, filters), fn ->
|
||||
Help.count_map_discovery_items(scope, filters)
|
||||
end)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
%{
|
||||
discovery_loading: false,
|
||||
requests: requests_page.entries,
|
||||
|
|
@ -267,7 +277,8 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
my_requests_cursor: my_requests_page.next_cursor,
|
||||
reputation: Trust.reputation(scope.user.id),
|
||||
map_items: Jason.encode!(map_items),
|
||||
map_result_count: Enum.reduce(map_items, 0, &(map_item_count(&1) + &2))
|
||||
map_result_count: Enum.reduce(map_items, 0, &(map_item_count(&1) + &2)),
|
||||
map_total_count: map_total_count
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -321,6 +332,11 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
)
|
||||
)
|
||||
|
||||
:ok =
|
||||
DiscoveryQueryCache.invalidate(
|
||||
map_total_cache_key(socket.assigns.current_scope, socket.assigns.filters)
|
||||
)
|
||||
|
||||
socket
|
||||
end
|
||||
|
||||
|
|
@ -328,6 +344,8 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
{:requests, scope.user.id, filters, discovery_mode, view_mode, viewport}
|
||||
end
|
||||
|
||||
defp map_total_cache_key(scope, filters), do: {:request_map_total, scope.user.id, filters}
|
||||
|
||||
defp empty_filters, do: %{"category_id" => "", "urgency" => "", "area" => ""}
|
||||
|
||||
defp normalize_filters(params) do
|
||||
|
|
@ -525,6 +543,11 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
count: @map_result_count
|
||||
)}
|
||||
</strong>
|
||||
<span class="ml-1 text-sm text-base-content/60">
|
||||
{gettext("of %{count} matching requests with map locations",
|
||||
count: @map_total_count
|
||||
)}
|
||||
</span>
|
||||
<p class="text-sm text-base-content/60">
|
||||
{gettext("Move the map, then search the newly visible area.")}
|
||||
</p>
|
||||
|
|
@ -680,6 +703,9 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
|
|||
data-map-retry-label={gettext("Try map again")}
|
||||
data-item-label={gettext("Open request")}
|
||||
data-cluster-label={gettext("%{count} requests", count: "%{count}")}
|
||||
data-cluster-max-zoom-label={
|
||||
gettext("Maximum zoom reached. Matching requests are listed beside the map.")
|
||||
}
|
||||
class="aid-map request-discovery-map"
|
||||
/>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ msgid "Active"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts.ex:231
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:33
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities"
|
||||
msgstr ""
|
||||
|
|
@ -215,7 +215,7 @@ msgstr ""
|
|||
msgid "Activity restored."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:427
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:204
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity type"
|
||||
|
|
@ -232,12 +232,12 @@ msgstr ""
|
|||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:429
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All activities"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All categories"
|
||||
msgstr ""
|
||||
|
|
@ -252,7 +252,7 @@ msgstr ""
|
|||
msgid "Another helper already accepted this request."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:508
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Any urgency"
|
||||
msgstr ""
|
||||
|
|
@ -367,16 +367,16 @@ msgstr ""
|
|||
msgid "Cancel request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:358
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:344
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:327
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:328
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:413
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:414
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:956
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:302
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Category"
|
||||
|
|
@ -459,7 +459,7 @@ msgstr ""
|
|||
msgid "Coffee and conversation"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:372
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
|
||||
msgstr ""
|
||||
|
|
@ -474,19 +474,19 @@ msgstr ""
|
|||
msgid "Community helpers"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:578
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:647
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:403
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:639
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:698
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:656
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Community member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:357
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:421
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:343
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:325
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:326
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:411
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:412
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:955
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:974
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -537,13 +537,13 @@ msgstr ""
|
|||
msgid "Confirming..."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:378
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:442
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create activity"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:650
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:722
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:180
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create an activity"
|
||||
|
|
@ -554,7 +554,7 @@ msgstr ""
|
|||
msgid "Create category"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:442
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create urgent request"
|
||||
msgstr ""
|
||||
|
|
@ -704,7 +704,7 @@ msgstr ""
|
|||
msgid "Explain what is already arranged and what the volunteer needs to do."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:369
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Find people to join"
|
||||
msgstr ""
|
||||
|
|
@ -865,8 +865,8 @@ msgstr ""
|
|||
msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:323
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:324
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:409
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:410
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:954
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "In progress"
|
||||
|
|
@ -989,8 +989,8 @@ msgstr ""
|
|||
msgid "Match and coordinate"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:321
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:322
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:408
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:952
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Matched"
|
||||
|
|
@ -1016,7 +1016,7 @@ msgstr ""
|
|||
msgid "Medicine requests"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:385
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:449
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
|
||||
msgstr ""
|
||||
|
|
@ -1067,12 +1067,12 @@ msgstr ""
|
|||
msgid "Moderator access is required."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:347
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "NEARBY MUTUAL AID"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:34
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby help"
|
||||
msgstr ""
|
||||
|
|
@ -1153,9 +1153,9 @@ msgid "Not for emergencies"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:748
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:311
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:312
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:423
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:397
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:509
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:943
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:944
|
||||
|
|
@ -1163,7 +1163,7 @@ msgstr ""
|
|||
msgid "Now"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:367
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:431
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OPTIONAL SOCIAL PLANS"
|
||||
msgstr ""
|
||||
|
|
@ -1181,12 +1181,12 @@ msgstr ""
|
|||
#: lib/who_need_help/notifications/text.ex:140
|
||||
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
|
||||
#: lib/who_need_help_web/controllers/support_html.ex:16
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:342
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:258
|
||||
#: lib/who_need_help_web/live/report_live.ex:104
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:319
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:320
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:406
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:951
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:237
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:438
|
||||
|
|
@ -1639,9 +1639,9 @@ msgid "Save profile"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:750
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:315
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:316
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:425
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:401
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:402
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:511
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:451
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:947
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:948
|
||||
|
|
@ -1904,7 +1904,7 @@ msgstr ""
|
|||
msgid "This interaction is blocked."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:363
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1022
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
|
||||
|
|
@ -1952,9 +1952,9 @@ msgid "To see sent emails, visit"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:749
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:313
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:314
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:424
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:400
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:510
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:450
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:945
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:946
|
||||
|
|
@ -2029,7 +2029,7 @@ msgid "Update email instructions"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:743
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:506
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:447
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Urgency"
|
||||
|
|
@ -2174,7 +2174,7 @@ msgstr ""
|
|||
msgid "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:348
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:434
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Who needs help?"
|
||||
msgstr ""
|
||||
|
|
@ -2292,7 +2292,7 @@ msgstr ""
|
|||
msgid "Your review is revealed only after both participants submit."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:630
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:724
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your trust summary"
|
||||
msgstr ""
|
||||
|
|
@ -2313,17 +2313,17 @@ msgstr ""
|
|||
msgid "before confirming."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:577
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:646
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:252
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:372
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:564
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "by %{name}"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/profile_live.ex:190
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:133
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:634
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:728
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1374
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "completed"
|
||||
|
|
@ -2359,7 +2359,7 @@ msgstr ""
|
|||
msgid "participants only"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:638
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:732
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1378
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "people"
|
||||
|
|
@ -2419,7 +2419,7 @@ msgstr ""
|
|||
#: lib/who_need_help_web/live/activity_live/show.ex:719
|
||||
#: lib/who_need_help_web/live/profile_live.ex:285
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:642
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:736
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1093
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1476
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2452,8 +2452,8 @@ msgstr ""
|
|||
msgid "Approved"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:329
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:330
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:415
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:416
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expired"
|
||||
msgstr ""
|
||||
|
|
@ -2506,8 +2506,8 @@ msgstr ""
|
|||
msgid "Load earlier messages"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:589
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:658
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:751
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:284
|
||||
#: lib/who_need_help_web/live/leaderboard_live.ex:91
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:465
|
||||
|
|
@ -2519,7 +2519,7 @@ msgstr ""
|
|||
#: lib/who_need_help_web/live/profile_live.ex:363
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:178
|
||||
#: lib/who_need_help_web/live/report_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:699
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:805
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:306
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:505
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2527,7 +2527,7 @@ msgid "Load more"
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:701
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1275
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Request details remain usable."
|
||||
|
|
@ -4516,44 +4516,44 @@ msgstr ""
|
|||
msgid "Exact point selected. Drag the red pin or click the map to move it."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:453
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:539
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} request in this map area"
|
||||
msgid_plural "%{count} requests in this map area"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:438
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:430
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:502
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:516
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Area or city"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:515
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:594
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:606
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose an area on the map"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:350
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:474
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:553
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "List"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:677
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Load more in this area"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:5
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:504
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:573
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:585
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Map"
|
||||
msgstr ""
|
||||
|
|
@ -4563,53 +4563,53 @@ msgstr ""
|
|||
msgid "Map preview"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:537
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:606
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:618
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move or zoom the map to search somewhere else."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:471
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:461
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:540
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:552
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move the map, then search the newly visible area."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:616
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open requests in this area"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:608
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only requests from the selected map area will be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:8
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:613
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:704
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:439
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:431
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:503
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Optional text search"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:592
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:683
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests without map coordinates appear only when their public area description matches the text search."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:475
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:465
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:556
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Results layout"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:625
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:693
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:719
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search this area"
|
||||
msgstr ""
|
||||
|
|
@ -4619,21 +4619,21 @@ msgstr ""
|
|||
msgid "Static preview"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:575
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Together"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:6
|
||||
#: lib/who_need_help_web/controllers/page_html/home.html.heex:160
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:609
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:681
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:355
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:414
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:546
|
||||
#: lib/who_need_help_web/live/notification_live.ex:602
|
||||
#: lib/who_need_help_web/live/notification_live.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:612
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:703
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:485
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1277
|
||||
|
|
@ -5022,7 +5022,7 @@ msgid "Nearby alert deleted."
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:456
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:376
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby requests"
|
||||
msgstr ""
|
||||
|
|
@ -5362,7 +5362,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:7
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:682
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:547
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1278
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -5927,7 +5927,7 @@ msgstr ""
|
|||
msgid "Delivery settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:569
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:660
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expires"
|
||||
msgstr ""
|
||||
|
|
@ -5947,29 +5947,29 @@ msgstr ""
|
|||
msgid "Notification sections"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:605
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:674
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activities"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activity in this map area"
|
||||
msgid_plural "%{count} activities in this map area"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:614
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:705
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} requests"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:645
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:717
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:393
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity views"
|
||||
msgstr ""
|
||||
|
|
@ -5984,12 +5984,12 @@ msgstr ""
|
|||
msgid "Confirm the safety guidance before asking nearby people for help."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:659
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:753
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create request"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:673
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:776
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create your first request"
|
||||
msgstr ""
|
||||
|
|
@ -6004,43 +6004,43 @@ msgstr ""
|
|||
msgid "Location"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My activities"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:377
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:653
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:747
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My requests"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby activities"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:643
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:715
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No activities yet"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open activities in this area"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:668
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:771
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No requests yet"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:596
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only activities from the selected map area will be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:673
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open activity"
|
||||
msgstr ""
|
||||
|
|
@ -6050,12 +6050,12 @@ msgstr ""
|
|||
msgid "Request form steps"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:371
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Request views"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:670
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:773
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests you publish or accept will appear here."
|
||||
msgstr ""
|
||||
|
|
@ -6080,7 +6080,7 @@ msgstr ""
|
|||
msgid "Share only as much location detail as helpers need."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:607
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Activity details remain usable."
|
||||
msgstr ""
|
||||
|
|
@ -6090,7 +6090,7 @@ msgstr ""
|
|||
msgid "Timing"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:749
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Track requests you created and requests you accepted as a helper."
|
||||
msgstr ""
|
||||
|
|
@ -6751,11 +6751,32 @@ msgstr ""
|
|||
msgid "Choose the meeting point on the map or use your current location."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:458
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:635
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:448
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:507
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:522
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:707
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:534
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:598
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:763
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loading results…"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:676
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching activities are listed beside the map."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:707
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching requests are listed beside the map."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching activities with map locations"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching requests with map locations"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ msgid "Active"
|
|||
msgstr "Active"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts.ex:231
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:33
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities"
|
||||
msgstr "Activities"
|
||||
|
|
@ -215,7 +215,7 @@ msgstr "Activity published. You approve every participant."
|
|||
msgid "Activity restored."
|
||||
msgstr "Activity restored."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:427
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:204
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity type"
|
||||
|
|
@ -232,12 +232,12 @@ msgstr "Add unverified link"
|
|||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:429
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All activities"
|
||||
msgstr "All activities"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All categories"
|
||||
msgstr "All categories"
|
||||
|
|
@ -252,7 +252,7 @@ msgstr "Already registered?"
|
|||
msgid "Another helper already accepted this request."
|
||||
msgstr "Another helper already accepted this request."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:508
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Any urgency"
|
||||
msgstr "Any urgency"
|
||||
|
|
@ -367,16 +367,16 @@ msgstr "Cancel activity"
|
|||
msgid "Cancel request"
|
||||
msgstr "Cancel request"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:358
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:344
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:327
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:328
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:413
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:414
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:956
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancelled"
|
||||
msgstr "Cancelled"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:302
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Category"
|
||||
|
|
@ -459,7 +459,7 @@ msgstr "Close"
|
|||
msgid "Coffee and conversation"
|
||||
msgstr "Coffee and conversation"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:372
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
|
||||
msgstr "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
|
||||
|
|
@ -474,19 +474,19 @@ msgstr "Comment (optional)"
|
|||
msgid "Community helpers"
|
||||
msgstr "Community helpers"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:578
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:647
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:403
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:639
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:698
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:656
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Community member"
|
||||
msgstr "Community member"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:357
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:421
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:343
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:325
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:326
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:411
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:412
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:955
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:974
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -537,13 +537,13 @@ msgstr "Confirmation instructions"
|
|||
msgid "Confirming..."
|
||||
msgstr "Confirming..."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:378
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:442
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create activity"
|
||||
msgstr "Create activity"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:650
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:722
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:180
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create an activity"
|
||||
|
|
@ -554,7 +554,7 @@ msgstr "Create an activity"
|
|||
msgid "Create category"
|
||||
msgstr "Create category"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:442
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create urgent request"
|
||||
msgstr "Create urgent request"
|
||||
|
|
@ -704,7 +704,7 @@ msgstr "Expires:"
|
|||
msgid "Explain what is already arranged and what the volunteer needs to do."
|
||||
msgstr "Explain what is already arranged and what the volunteer needs to do."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:369
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Find people to join"
|
||||
msgstr "Find people to join"
|
||||
|
|
@ -865,8 +865,8 @@ msgstr "Impersonation"
|
|||
msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
|
||||
msgstr "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:323
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:324
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:409
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:410
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:954
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "In progress"
|
||||
|
|
@ -989,8 +989,8 @@ msgstr "Mark completed"
|
|||
msgid "Match and coordinate"
|
||||
msgstr "Match and coordinate"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:321
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:322
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:408
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:952
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Matched"
|
||||
|
|
@ -1016,7 +1016,7 @@ msgstr "Medicine pickup remains the first priority. Roadside categories use the
|
|||
msgid "Medicine requests"
|
||||
msgstr "Medicine requests"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:385
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:449
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
|
||||
msgstr "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
|
||||
|
|
@ -1067,12 +1067,12 @@ msgstr "Moderator"
|
|||
msgid "Moderator access is required."
|
||||
msgstr "Moderator access is required."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:347
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "NEARBY MUTUAL AID"
|
||||
msgstr "NEARBY MUTUAL AID"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:34
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby help"
|
||||
msgstr "Nearby help"
|
||||
|
|
@ -1153,9 +1153,9 @@ msgid "Not for emergencies"
|
|||
msgstr "Not for emergencies"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:748
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:311
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:312
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:423
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:397
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:509
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:943
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:944
|
||||
|
|
@ -1163,7 +1163,7 @@ msgstr "Not for emergencies"
|
|||
msgid "Now"
|
||||
msgstr "Now"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:367
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:431
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OPTIONAL SOCIAL PLANS"
|
||||
msgstr "OPTIONAL SOCIAL PLANS"
|
||||
|
|
@ -1181,12 +1181,12 @@ msgstr "Only the organizer and approved participants can read or send these mess
|
|||
#: lib/who_need_help/notifications/text.ex:140
|
||||
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
|
||||
#: lib/who_need_help_web/controllers/support_html.ex:16
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:342
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:258
|
||||
#: lib/who_need_help_web/live/report_live.ex:104
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:319
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:320
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:406
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:951
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:237
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:438
|
||||
|
|
@ -1639,9 +1639,9 @@ msgid "Save profile"
|
|||
msgstr "Save profile"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:750
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:315
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:316
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:425
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:401
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:402
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:511
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:451
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:947
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:948
|
||||
|
|
@ -1904,7 +1904,7 @@ msgstr "This activity state has already changed."
|
|||
msgid "This interaction is blocked."
|
||||
msgstr "This interaction is blocked."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:363
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1022
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
|
||||
|
|
@ -1952,9 +1952,9 @@ msgid "To see sent emails, visit"
|
|||
msgstr "To see sent emails, visit"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:749
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:313
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:314
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:424
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:400
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:510
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:450
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:945
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:946
|
||||
|
|
@ -2029,7 +2029,7 @@ msgid "Update email instructions"
|
|||
msgstr "Update email instructions"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:743
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:506
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:447
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Urgency"
|
||||
|
|
@ -2174,7 +2174,7 @@ msgstr "Who Need Help home"
|
|||
msgid "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
|
||||
msgstr "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:348
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:434
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Who needs help?"
|
||||
msgstr "Who needs help?"
|
||||
|
|
@ -2292,7 +2292,7 @@ msgstr "Your requests"
|
|||
msgid "Your review is revealed only after both participants submit."
|
||||
msgstr "Your review is revealed only after both participants submit."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:630
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:724
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your trust summary"
|
||||
msgstr "Your trust summary"
|
||||
|
|
@ -2313,17 +2313,17 @@ msgstr "activity message"
|
|||
msgid "before confirming."
|
||||
msgstr "before confirming."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:577
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:646
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:252
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:372
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:564
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "by %{name}"
|
||||
msgstr "by %{name}"
|
||||
|
||||
#: lib/who_need_help_web/live/profile_live.ex:190
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:133
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:634
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:728
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1374
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "completed"
|
||||
|
|
@ -2359,7 +2359,7 @@ msgstr "organizer"
|
|||
msgid "participants only"
|
||||
msgstr "participants only"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:638
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:732
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1378
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "people"
|
||||
|
|
@ -2419,7 +2419,7 @@ msgstr "urgent"
|
|||
#: lib/who_need_help_web/live/activity_live/show.ex:719
|
||||
#: lib/who_need_help_web/live/profile_live.ex:285
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:642
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:736
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1093
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1476
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2452,8 +2452,8 @@ msgstr "← Requests"
|
|||
msgid "Approved"
|
||||
msgstr "Approved"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:329
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:330
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:415
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:416
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expired"
|
||||
msgstr "Expired"
|
||||
|
|
@ -2506,8 +2506,8 @@ msgstr "Unusual action rate"
|
|||
msgid "Load earlier messages"
|
||||
msgstr "Load earlier messages"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:589
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:658
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:751
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:284
|
||||
#: lib/who_need_help_web/live/leaderboard_live.ex:91
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:465
|
||||
|
|
@ -2519,7 +2519,7 @@ msgstr "Load earlier messages"
|
|||
#: lib/who_need_help_web/live/profile_live.ex:363
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:178
|
||||
#: lib/who_need_help_web/live/report_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:699
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:805
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:306
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:505
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2527,7 +2527,7 @@ msgid "Load more"
|
|||
msgstr "Load more"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:701
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1275
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Request details remain usable."
|
||||
|
|
@ -4516,44 +4516,44 @@ msgstr ""
|
|||
msgid "Exact point selected. Drag the red pin or click the map to move it."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:453
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:539
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} request in this map area"
|
||||
msgid_plural "%{count} requests in this map area"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:438
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:430
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:502
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:516
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Area or city"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:515
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:594
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:606
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose an area on the map"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:350
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:474
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:553
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "List"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:677
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Load more in this area"
|
||||
msgstr "Load more in this area"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:5
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:504
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:573
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:585
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Map"
|
||||
msgstr ""
|
||||
|
|
@ -4563,53 +4563,53 @@ msgstr ""
|
|||
msgid "Map preview"
|
||||
msgstr "Map preview"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:537
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:606
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:618
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move or zoom the map to search somewhere else."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:471
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:461
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:540
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:552
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move the map, then search the newly visible area."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:616
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open requests in this area"
|
||||
msgstr "No open requests in this area"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:608
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only requests from the selected map area will be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:8
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:613
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:704
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open request"
|
||||
msgstr "Open request"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:439
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:431
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:503
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Optional text search"
|
||||
msgstr "Optional text search"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:592
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:683
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests without map coordinates appear only when their public area description matches the text search."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:475
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:465
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:556
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Results layout"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:625
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:693
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:719
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search this area"
|
||||
msgstr ""
|
||||
|
|
@ -4619,21 +4619,21 @@ msgstr ""
|
|||
msgid "Static preview"
|
||||
msgstr "Static preview"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:575
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Together"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:6
|
||||
#: lib/who_need_help_web/controllers/page_html/home.html.heex:160
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:609
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:681
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:355
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:414
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:546
|
||||
#: lib/who_need_help_web/live/notification_live.ex:602
|
||||
#: lib/who_need_help_web/live/notification_live.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:612
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:703
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:485
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1277
|
||||
|
|
@ -5022,7 +5022,7 @@ msgid "Nearby alert deleted."
|
|||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:456
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:376
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby requests"
|
||||
msgstr ""
|
||||
|
|
@ -5362,7 +5362,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
|
|||
msgstr "WebGL is unavailable in this browser, so the full map cannot start. Select an example below or retry the map."
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:7
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:682
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:547
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1278
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -5927,7 +5927,7 @@ msgstr "Your review is now visible"
|
|||
msgid "Delivery settings"
|
||||
msgstr "Delivery settings"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:569
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:660
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expires"
|
||||
msgstr "Expires"
|
||||
|
|
@ -5947,29 +5947,29 @@ msgstr "Nearby alerts"
|
|||
msgid "Notification sections"
|
||||
msgstr "Notification sections"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:605
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:674
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activities"
|
||||
msgstr "%{count} activities"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activity in this map area"
|
||||
msgid_plural "%{count} activities in this map area"
|
||||
msgstr[0] "%{count} activity in this map area"
|
||||
msgstr[1] "%{count} activities in this map area"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:614
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:705
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} requests"
|
||||
msgstr "%{count} requests"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:645
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:717
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
|
||||
msgstr "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:393
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity views"
|
||||
msgstr "Activity views"
|
||||
|
|
@ -5984,12 +5984,12 @@ msgstr "Choose a category and describe the task clearly."
|
|||
msgid "Confirm the safety guidance before asking nearby people for help."
|
||||
msgstr "Confirm the safety guidance before asking nearby people for help."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:659
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:753
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create request"
|
||||
msgstr "Create request"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:673
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:776
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create your first request"
|
||||
msgstr "Create your first request"
|
||||
|
|
@ -6004,43 +6004,43 @@ msgstr "Help details"
|
|||
msgid "Location"
|
||||
msgstr "Location"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My activities"
|
||||
msgstr "My activities"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:377
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:653
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:747
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My requests"
|
||||
msgstr "My requests"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby activities"
|
||||
msgstr "Nearby activities"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:643
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:715
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No activities yet"
|
||||
msgstr "No activities yet"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open activities in this area"
|
||||
msgstr "No open activities in this area"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:668
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:771
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No requests yet"
|
||||
msgstr "No requests yet"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:596
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only activities from the selected map area will be loaded."
|
||||
msgstr "Only activities from the selected map area will be loaded."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:673
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open activity"
|
||||
msgstr "Open activity"
|
||||
|
|
@ -6050,12 +6050,12 @@ msgstr "Open activity"
|
|||
msgid "Request form steps"
|
||||
msgstr "Request form steps"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:371
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Request views"
|
||||
msgstr "Request views"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:670
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:773
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests you publish or accept will appear here."
|
||||
msgstr "Requests you publish or accept will appear here."
|
||||
|
|
@ -6080,7 +6080,7 @@ msgstr "Set urgency and when this request should close."
|
|||
msgid "Share only as much location detail as helpers need."
|
||||
msgstr "Share only as much location detail as helpers need."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:607
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Activity details remain usable."
|
||||
msgstr "The map is unavailable in this browser. Activity details remain usable."
|
||||
|
|
@ -6090,7 +6090,7 @@ msgstr "The map is unavailable in this browser. Activity details remain usable."
|
|||
msgid "Timing"
|
||||
msgstr "Timing"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:749
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Track requests you created and requests you accepted as a helper."
|
||||
msgstr "Track requests you created and requests you accepted as a helper."
|
||||
|
|
@ -6751,11 +6751,32 @@ msgstr "What should people see before approval?"
|
|||
msgid "Choose the meeting point on the map or use your current location."
|
||||
msgstr "Choose the meeting point on the map or use your current location."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:458
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:635
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:448
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:507
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:522
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:707
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:534
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:598
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:763
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loading results…"
|
||||
msgstr "Loading results…"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:676
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching activities are listed beside the map."
|
||||
msgstr "Maximum zoom reached. Matching activities are listed beside the map."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:707
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching requests are listed beside the map."
|
||||
msgstr "Maximum zoom reached. Matching requests are listed beside the map."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching activities with map locations"
|
||||
msgstr "of %{count} matching activities with map locations"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching requests with map locations"
|
||||
msgstr "of %{count} matching requests with map locations"
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ msgid "Active"
|
|||
msgstr "Активный"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts.ex:231
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:33
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities"
|
||||
msgstr "Активности"
|
||||
|
|
@ -226,7 +226,7 @@ msgstr "Активность опубликована. Вы одобряете
|
|||
msgid "Activity restored."
|
||||
msgstr "Активность восстановлена."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:427
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:204
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity type"
|
||||
|
|
@ -243,12 +243,12 @@ msgstr "Добавить неподтверждённую ссылку"
|
|||
msgid "Administrator"
|
||||
msgstr "Администратор"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:429
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All activities"
|
||||
msgstr "Все активности"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All categories"
|
||||
msgstr "Все категории"
|
||||
|
|
@ -263,7 +263,7 @@ msgstr "Уже зарегистрированы?"
|
|||
msgid "Another helper already accepted this request."
|
||||
msgstr "Другой помощник уже принял эту заявку."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:508
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Any urgency"
|
||||
msgstr "Любая срочность"
|
||||
|
|
@ -383,16 +383,16 @@ msgstr "Отменить активность"
|
|||
msgid "Cancel request"
|
||||
msgstr "Отменить заявку"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:358
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:344
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:327
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:328
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:413
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:414
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:956
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancelled"
|
||||
msgstr "Отменено"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:302
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Category"
|
||||
|
|
@ -476,7 +476,7 @@ msgstr "Закрыть"
|
|||
msgid "Coffee and conversation"
|
||||
msgstr "Кофе и разговоры"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:372
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
|
||||
msgstr ""
|
||||
|
|
@ -493,19 +493,19 @@ msgstr "Комментарий (необязательно)"
|
|||
msgid "Community helpers"
|
||||
msgstr "Помощники сообщества"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:578
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:647
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:403
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:639
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:698
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:656
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Community member"
|
||||
msgstr "Член сообщества"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:357
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:421
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:343
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:325
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:326
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:411
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:412
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:955
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:974
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -558,13 +558,13 @@ msgstr "Инструкции по подтверждению"
|
|||
msgid "Confirming..."
|
||||
msgstr "Подтверждение…"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:378
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:442
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create activity"
|
||||
msgstr "Создать активность"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:650
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:722
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:180
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create an activity"
|
||||
|
|
@ -575,7 +575,7 @@ msgstr "Создать активность"
|
|||
msgid "Create category"
|
||||
msgstr "Создать категорию"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:442
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create urgent request"
|
||||
msgstr "Создать срочную заявку"
|
||||
|
|
@ -737,7 +737,7 @@ msgstr "Истекает:"
|
|||
msgid "Explain what is already arranged and what the volunteer needs to do."
|
||||
msgstr "Объясните, что уже сделано и что нужно сделать волонтеру."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:369
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Find people to join"
|
||||
msgstr "Найти компанию"
|
||||
|
|
@ -938,8 +938,8 @@ msgstr ""
|
|||
"Android показывает постоянное уведомление и может продолжать передачу после "
|
||||
"сворачивания. При остановке передачи исходные координаты удаляются."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:323
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:324
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:409
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:410
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:954
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "In progress"
|
||||
|
|
@ -1069,8 +1069,8 @@ msgstr "Отметить завершённой"
|
|||
msgid "Match and coordinate"
|
||||
msgstr "Найдите помощника и согласуйте детали"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:321
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:322
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:408
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:952
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Matched"
|
||||
|
|
@ -1099,7 +1099,7 @@ msgstr ""
|
|||
msgid "Medicine requests"
|
||||
msgstr "Заявки на лекарства"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:385
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:449
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
|
||||
msgstr ""
|
||||
|
|
@ -1153,12 +1153,12 @@ msgstr "Модератор"
|
|||
msgid "Moderator access is required."
|
||||
msgstr "Требуется доступ модератора."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:347
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "NEARBY MUTUAL AID"
|
||||
msgstr "ВЗАИМОПОМОЩЬ РЯДОМ"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:34
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby help"
|
||||
msgstr "Помощь рядом"
|
||||
|
|
@ -1241,9 +1241,9 @@ msgid "Not for emergencies"
|
|||
msgstr "Не для чрезвычайных ситуаций"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:748
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:311
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:312
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:423
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:397
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:509
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:943
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:944
|
||||
|
|
@ -1251,7 +1251,7 @@ msgstr "Не для чрезвычайных ситуаций"
|
|||
msgid "Now"
|
||||
msgstr "Сейчас"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:367
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:431
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OPTIONAL SOCIAL PLANS"
|
||||
msgstr "НЕОБЯЗАТЕЛЬНЫЕ СОВМЕСТНЫЕ ПЛАНЫ"
|
||||
|
|
@ -1271,12 +1271,12 @@ msgstr ""
|
|||
#: lib/who_need_help/notifications/text.ex:140
|
||||
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
|
||||
#: lib/who_need_help_web/controllers/support_html.ex:16
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:342
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:258
|
||||
#: lib/who_need_help_web/live/report_live.ex:104
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:319
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:320
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:406
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:951
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:237
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:438
|
||||
|
|
@ -1748,9 +1748,9 @@ msgid "Save profile"
|
|||
msgstr "Сохранить профиль"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:750
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:315
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:316
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:425
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:401
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:402
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:511
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:451
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:947
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:948
|
||||
|
|
@ -2022,7 +2022,7 @@ msgstr "Состояние этой активности уже изменило
|
|||
msgid "This interaction is blocked."
|
||||
msgstr "Это взаимодействие заблокировано."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:363
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1022
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
|
||||
|
|
@ -2075,9 +2075,9 @@ msgid "To see sent emails, visit"
|
|||
msgstr "Чтобы посмотреть отправленные письма, откройте"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:749
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:313
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:314
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:424
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:400
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:510
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:450
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:945
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:946
|
||||
|
|
@ -2152,7 +2152,7 @@ msgid "Update email instructions"
|
|||
msgstr "Инструкции для изменения электронной почты"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:743
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:506
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:447
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Urgency"
|
||||
|
|
@ -2308,7 +2308,7 @@ msgstr ""
|
|||
"через подписку ChatGPT разработчика. Ключ OpenAI API и резервный вариант API"
|
||||
" с оплатой по использованию не применяются."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:348
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:434
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Who needs help?"
|
||||
msgstr "Кому нужна помощь?"
|
||||
|
|
@ -2432,7 +2432,7 @@ msgid "Your review is revealed only after both participants submit."
|
|||
msgstr ""
|
||||
"Ваш отзыв откроется только после того, как его отправят оба участника."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:630
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:724
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your trust summary"
|
||||
msgstr "Ваши показатели доверия"
|
||||
|
|
@ -2453,17 +2453,17 @@ msgstr "сообщение активности"
|
|||
msgid "before confirming."
|
||||
msgstr "перед подтверждением."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:577
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:646
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:252
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:372
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:564
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "by %{name}"
|
||||
msgstr "автор: %{name}"
|
||||
|
||||
#: lib/who_need_help_web/live/profile_live.ex:190
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:133
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:634
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:728
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1374
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "completed"
|
||||
|
|
@ -2499,7 +2499,7 @@ msgstr "организатор"
|
|||
msgid "participants only"
|
||||
msgstr "только участники"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:638
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:732
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1378
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "people"
|
||||
|
|
@ -2559,7 +2559,7 @@ msgstr "срочно"
|
|||
#: lib/who_need_help_web/live/activity_live/show.ex:719
|
||||
#: lib/who_need_help_web/live/profile_live.ex:285
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:642
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:736
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1093
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1476
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2592,8 +2592,8 @@ msgstr "← Заявки"
|
|||
msgid "Approved"
|
||||
msgstr "Одобрено"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:329
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:330
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:415
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:416
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expired"
|
||||
msgstr "Истекло"
|
||||
|
|
@ -2646,8 +2646,8 @@ msgstr "Необычная частота действий"
|
|||
msgid "Load earlier messages"
|
||||
msgstr "Загрузить предыдущие сообщения"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:589
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:658
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:751
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:284
|
||||
#: lib/who_need_help_web/live/leaderboard_live.ex:91
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:465
|
||||
|
|
@ -2659,7 +2659,7 @@ msgstr "Загрузить предыдущие сообщения"
|
|||
#: lib/who_need_help_web/live/profile_live.ex:363
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:178
|
||||
#: lib/who_need_help_web/live/report_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:699
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:805
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:306
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:505
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2667,7 +2667,7 @@ msgid "Load more"
|
|||
msgstr "Загрузить ещё"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:701
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1275
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Request details remain usable."
|
||||
|
|
@ -4656,7 +4656,7 @@ msgstr "Перетащите, чтобы переместить точную т
|
|||
msgid "Exact point selected. Drag the red pin or click the map to move it."
|
||||
msgstr "Точная точка выбрана. Перетащите красный маркер или нажмите на карту, чтобы переместить её."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:453
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:539
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} request in this map area"
|
||||
msgid_plural "%{count} requests in this map area"
|
||||
|
|
@ -4664,37 +4664,37 @@ msgstr[0] "%{count} заявка в этой области карты"
|
|||
msgstr[1] "%{count} заявки в этой области карты"
|
||||
msgstr[2] "%{count} заявок в этой области карты"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:438
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:430
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:502
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:516
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Area or city"
|
||||
msgstr "Район или город"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:515
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:594
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:606
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose an area on the map"
|
||||
msgstr "Выберите область на карте"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:350
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
|
||||
msgstr "Просматривайте безопасные для приватности области и точные публичные места встречи, выбранные авторами заявок."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:474
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:553
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "List"
|
||||
msgstr "Список"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:677
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Load more in this area"
|
||||
msgstr "Загрузить ещё в этой области"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:5
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:504
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:573
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:585
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Map"
|
||||
msgstr "Карта"
|
||||
|
|
@ -4704,53 +4704,53 @@ msgstr "Карта"
|
|||
msgid "Map preview"
|
||||
msgstr "Предпросмотр карты"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:537
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:606
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:618
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move or zoom the map to search somewhere else."
|
||||
msgstr "Переместите или масштабируйте карту, чтобы искать в другом месте."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:471
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:461
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:540
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:552
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move the map, then search the newly visible area."
|
||||
msgstr "Переместите карту, затем выполните поиск в новой видимой области."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:616
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open requests in this area"
|
||||
msgstr "В этой области нет открытых заявок"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:608
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only requests from the selected map area will be loaded."
|
||||
msgstr "Будут загружены только заявки из выбранной области карты."
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:8
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:613
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:704
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open request"
|
||||
msgstr "Открыть заявку"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:439
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:431
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:503
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Optional text search"
|
||||
msgstr "Необязательный текстовый поиск"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:592
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:683
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests without map coordinates appear only when their public area description matches the text search."
|
||||
msgstr "Заявки без координат на карте отображаются только тогда, когда описание публичной области совпадает с текстовым поиском."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:475
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:465
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:556
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Results layout"
|
||||
msgstr "Расположение результатов"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:625
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:693
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:719
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search this area"
|
||||
msgstr "Искать в этой области"
|
||||
|
|
@ -4760,21 +4760,21 @@ msgstr "Искать в этой области"
|
|||
msgid "Static preview"
|
||||
msgstr "Статический просмотр"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:575
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Together"
|
||||
msgstr "Вместе"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:6
|
||||
#: lib/who_need_help_web/controllers/page_html/home.html.heex:160
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:609
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:681
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:355
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:414
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:546
|
||||
#: lib/who_need_help_web/live/notification_live.ex:602
|
||||
#: lib/who_need_help_web/live/notification_live.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:612
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:703
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:485
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1277
|
||||
|
|
@ -5164,7 +5164,7 @@ msgid "Nearby alert deleted."
|
|||
msgstr "Уведомление поблизости удалено."
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:456
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:376
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby requests"
|
||||
msgstr "Заявки поблизости"
|
||||
|
|
@ -5504,7 +5504,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
|
|||
msgstr "WebGL недоступен в этом браузере, поэтому полная карта не может запуститься. Выберите пример ниже или попробуйте запустить карту ещё раз."
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:7
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:682
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:547
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1278
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -6069,7 +6069,7 @@ msgstr "Теперь ваш отзыв виден"
|
|||
msgid "Delivery settings"
|
||||
msgstr "Настройки доставки"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:569
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:660
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expires"
|
||||
msgstr "Истекает"
|
||||
|
|
@ -6089,12 +6089,12 @@ msgstr "Оповещения рядом"
|
|||
msgid "Notification sections"
|
||||
msgstr "Разделы уведомлений"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:605
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:674
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activities"
|
||||
msgstr "Активностей: %{count}"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activity in this map area"
|
||||
msgid_plural "%{count} activities in this map area"
|
||||
|
|
@ -6102,17 +6102,17 @@ msgstr[0] "%{count} активность в этой области карты"
|
|||
msgstr[1] "%{count} активности в этой области карты"
|
||||
msgstr[2] "%{count} активностей в этой области карты"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:614
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:705
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} requests"
|
||||
msgstr "Заявок: %{count}"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:645
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:717
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
|
||||
msgstr "Созданные вами активности и те, к которым вы попросили присоединиться, появятся здесь. После выхода активность исчезнет из списка."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:393
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity views"
|
||||
msgstr "Разделы активностей"
|
||||
|
|
@ -6127,12 +6127,12 @@ msgstr "Выберите категорию и понятно опишите з
|
|||
msgid "Confirm the safety guidance before asking nearby people for help."
|
||||
msgstr "Подтвердите правила безопасности перед публикацией заявки."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:659
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:753
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create request"
|
||||
msgstr "Создать заявку"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:673
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:776
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create your first request"
|
||||
msgstr "Создать первую заявку"
|
||||
|
|
@ -6147,43 +6147,43 @@ msgstr "Что нужно сделать"
|
|||
msgid "Location"
|
||||
msgstr "Место"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My activities"
|
||||
msgstr "Мои активности"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:377
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:653
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:747
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My requests"
|
||||
msgstr "Мои заявки"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby activities"
|
||||
msgstr "Активности рядом"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:643
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:715
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No activities yet"
|
||||
msgstr "Активностей пока нет"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open activities in this area"
|
||||
msgstr "В этой области нет открытых активностей"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:668
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:771
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No requests yet"
|
||||
msgstr "Заявок пока нет"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:596
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only activities from the selected map area will be loaded."
|
||||
msgstr "Будут загружены только активности из выбранной области карты."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:673
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open activity"
|
||||
msgstr "Открыть активность"
|
||||
|
|
@ -6193,12 +6193,12 @@ msgstr "Открыть активность"
|
|||
msgid "Request form steps"
|
||||
msgstr "Шаги создания заявки"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:371
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Request views"
|
||||
msgstr "Разделы заявок"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:670
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:773
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests you publish or accept will appear here."
|
||||
msgstr "Здесь появятся заявки, которые вы создали или приняли."
|
||||
|
|
@ -6223,7 +6223,7 @@ msgstr "Укажите срочность и время закрытия зая
|
|||
msgid "Share only as much location detail as helpers need."
|
||||
msgstr "Покажите помощникам только необходимую информацию о месте."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:607
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Activity details remain usable."
|
||||
msgstr "Карта недоступна в этом браузере. Информация об активностях по-прежнему доступна."
|
||||
|
|
@ -6233,7 +6233,7 @@ msgstr "Карта недоступна в этом браузере. Инфор
|
|||
msgid "Timing"
|
||||
msgstr "Время"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:749
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Track requests you created and requests you accepted as a helper."
|
||||
msgstr "Следите за созданными заявками и за теми, которые вы приняли как помощник."
|
||||
|
|
@ -6894,11 +6894,32 @@ msgstr "Что должны видеть люди до одобрения?"
|
|||
msgid "Choose the meeting point on the map or use your current location."
|
||||
msgstr "Выберите место встречи на карте или используйте своё текущее местоположение."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:458
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:635
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:448
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:507
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:522
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:707
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:534
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:598
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:763
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loading results…"
|
||||
msgstr "Загружаем результаты…"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:676
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching activities are listed beside the map."
|
||||
msgstr "Достигнут максимальный масштаб. Подходящие мероприятия перечислены рядом с картой."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:707
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching requests are listed beside the map."
|
||||
msgstr "Достигнут максимальный масштаб. Подходящие заявки перечислены рядом с картой."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching activities with map locations"
|
||||
msgstr "из %{count} подходящих мероприятий с местоположением на карте"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching requests with map locations"
|
||||
msgstr "из %{count} подходящих заявок с местоположением на карте"
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ msgid "Active"
|
|||
msgstr "Активний"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts.ex:231
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:33
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities"
|
||||
msgstr "Активності"
|
||||
|
|
@ -226,7 +226,7 @@ msgstr "Активність опубліковано. Ви схвалюєте
|
|||
msgid "Activity restored."
|
||||
msgstr "Активність відновлено."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:427
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:204
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity type"
|
||||
|
|
@ -243,12 +243,12 @@ msgstr "Додати непідтверджене посилання"
|
|||
msgid "Administrator"
|
||||
msgstr "Адміністратор"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:429
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All activities"
|
||||
msgstr "Усі активності"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:493
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "All categories"
|
||||
msgstr "Усі категорії"
|
||||
|
|
@ -263,7 +263,7 @@ msgstr "Уже зареєстровані?"
|
|||
msgid "Another helper already accepted this request."
|
||||
msgstr "Інший помічник уже прийняв цю заявку."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:508
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Any urgency"
|
||||
msgstr "Будь-яка терміновість"
|
||||
|
|
@ -383,16 +383,16 @@ msgstr "Скасувати активність"
|
|||
msgid "Cancel request"
|
||||
msgstr "Скасувати запит"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:358
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:422
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:344
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:327
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:328
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:413
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:414
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:956
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancelled"
|
||||
msgstr "Скасовано"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:491
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:302
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Category"
|
||||
|
|
@ -476,7 +476,7 @@ msgstr "Закрити"
|
|||
msgid "Coffee and conversation"
|
||||
msgstr "Кава й розмова"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:372
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
|
||||
msgstr ""
|
||||
|
|
@ -493,19 +493,19 @@ msgstr "Коментар (необов’язково)"
|
|||
msgid "Community helpers"
|
||||
msgstr "Помічники спільноти"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:578
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:647
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:403
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:639
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:698
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:656
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Community member"
|
||||
msgstr "Член спільноти"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:357
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:421
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:343
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:325
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:326
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:411
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:412
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:955
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:974
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -559,13 +559,13 @@ msgstr "Інструкції для підтвердження"
|
|||
msgid "Confirming..."
|
||||
msgstr "Підтвердження…"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:378
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:442
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create activity"
|
||||
msgstr "Створити активність"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:650
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:722
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:180
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create an activity"
|
||||
|
|
@ -576,7 +576,7 @@ msgstr "Створити активність"
|
|||
msgid "Create category"
|
||||
msgstr "Створити категорію"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:442
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create urgent request"
|
||||
msgstr "Створити термінову заявку"
|
||||
|
|
@ -736,7 +736,7 @@ msgstr "Спливає:"
|
|||
msgid "Explain what is already arranged and what the volunteer needs to do."
|
||||
msgstr "Поясніть, що вже організовано й що має зробити волонтер."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:369
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Find people to join"
|
||||
msgstr "Знайти компанію"
|
||||
|
|
@ -936,8 +936,8 @@ msgstr ""
|
|||
"Android показує постійне сповіщення й може продовжувати передавання після "
|
||||
"згортання. Після зупинення передавання вихідні координати видаляються."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:323
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:324
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:409
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:410
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:954
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "In progress"
|
||||
|
|
@ -1065,8 +1065,8 @@ msgstr "Позначити завершеною"
|
|||
msgid "Match and coordinate"
|
||||
msgstr "Знайдіть помічника й узгодьте деталі"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:321
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:322
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:407
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:408
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:952
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Matched"
|
||||
|
|
@ -1095,7 +1095,7 @@ msgstr ""
|
|||
msgid "Medicine requests"
|
||||
msgstr "Запити на ліки"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:385
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:449
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
|
||||
msgstr ""
|
||||
|
|
@ -1149,12 +1149,12 @@ msgstr "Модератор"
|
|||
msgid "Moderator access is required."
|
||||
msgstr "Потрібен доступ модератора."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:347
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:433
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "NEARBY MUTUAL AID"
|
||||
msgstr "ВЗАЄМОДОПОМОГА ПОРУЧ"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:34
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby help"
|
||||
msgstr "Допомога поруч"
|
||||
|
|
@ -1237,9 +1237,9 @@ msgid "Not for emergencies"
|
|||
msgstr "Не для екстрених ситуацій"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:748
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:311
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:312
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:423
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:397
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:509
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:943
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:944
|
||||
|
|
@ -1247,7 +1247,7 @@ msgstr "Не для екстрених ситуацій"
|
|||
msgid "Now"
|
||||
msgstr "Зараз"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:367
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:431
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OPTIONAL SOCIAL PLANS"
|
||||
msgstr "НЕОБОВ’ЯЗКОВІ СПІЛЬНІ ПЛАНИ"
|
||||
|
|
@ -1267,12 +1267,12 @@ msgstr ""
|
|||
#: lib/who_need_help/notifications/text.ex:140
|
||||
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
|
||||
#: lib/who_need_help_web/controllers/support_html.ex:16
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:356
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:342
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:258
|
||||
#: lib/who_need_help_web/live/report_live.ex:104
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:319
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:320
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:405
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:406
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:951
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:237
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:438
|
||||
|
|
@ -1745,9 +1745,9 @@ msgid "Save profile"
|
|||
msgstr "Зберегти профіль"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:750
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:315
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:316
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:425
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:401
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:402
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:511
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:451
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:947
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:948
|
||||
|
|
@ -2019,7 +2019,7 @@ msgstr "Цей стан активності вже змінено."
|
|||
msgid "This interaction is blocked."
|
||||
msgstr "Ця взаємодія заблокована."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:363
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:449
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1022
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
|
||||
|
|
@ -2072,9 +2072,9 @@ msgid "To see sent emails, visit"
|
|||
msgstr "Щоб переглянути надіслані листи, відкрийте"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:749
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:313
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:314
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:424
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:400
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:510
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:450
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:945
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:946
|
||||
|
|
@ -2149,7 +2149,7 @@ msgid "Update email instructions"
|
|||
msgstr "Інструкції для зміни електронної пошти"
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:743
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:420
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:506
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:447
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Urgency"
|
||||
|
|
@ -2304,7 +2304,7 @@ msgstr ""
|
|||
"через підписку ChatGPT розробника. Ключ OpenAI API та резервний варіант API "
|
||||
"з оплатою за використання не застосовуються."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:348
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:434
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Who needs help?"
|
||||
msgstr "Хто потребує допомоги?"
|
||||
|
|
@ -2426,7 +2426,7 @@ msgid "Your review is revealed only after both participants submit."
|
|||
msgstr ""
|
||||
"Ваш відгук відкриється лише після того, як його надішлють обидва учасники."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:630
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:724
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your trust summary"
|
||||
msgstr "Ваші показники довіри"
|
||||
|
|
@ -2447,17 +2447,17 @@ msgstr "повідомлення активності"
|
|||
msgid "before confirming."
|
||||
msgstr "перед підтвердженням."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:577
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:646
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:252
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:372
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:564
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "by %{name}"
|
||||
msgstr "автор: %{name}"
|
||||
|
||||
#: lib/who_need_help_web/live/profile_live.ex:190
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:133
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:634
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:728
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1374
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "completed"
|
||||
|
|
@ -2493,7 +2493,7 @@ msgstr "організатор"
|
|||
msgid "participants only"
|
||||
msgstr "лише учасники"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:638
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:732
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1378
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "people"
|
||||
|
|
@ -2553,7 +2553,7 @@ msgstr "терміново"
|
|||
#: lib/who_need_help_web/live/activity_live/show.ex:719
|
||||
#: lib/who_need_help_web/live/profile_live.ex:285
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:642
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:736
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1093
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1476
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2586,8 +2586,8 @@ msgstr "← Заявки"
|
|||
msgid "Approved"
|
||||
msgstr "Схвалено"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:329
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:330
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:415
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:416
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expired"
|
||||
msgstr "Спливло"
|
||||
|
|
@ -2640,8 +2640,8 @@ msgstr "Незвична частота дій"
|
|||
msgid "Load earlier messages"
|
||||
msgstr "Завантажити попередні повідомлення"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:589
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:658
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:751
|
||||
#: lib/who_need_help_web/live/category_proposal_live.ex:284
|
||||
#: lib/who_need_help_web/live/leaderboard_live.ex:91
|
||||
#: lib/who_need_help_web/live/moderation_live.ex:465
|
||||
|
|
@ -2653,7 +2653,7 @@ msgstr "Завантажити попередні повідомлення"
|
|||
#: lib/who_need_help_web/live/profile_live.ex:363
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:178
|
||||
#: lib/who_need_help_web/live/report_live.ex:98
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:699
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:805
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:306
|
||||
#: lib/who_need_help_web/live/support_operations_live.ex:505
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -2661,7 +2661,7 @@ msgid "Load more"
|
|||
msgstr "Завантажити ще"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:701
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1275
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Request details remain usable."
|
||||
|
|
@ -4650,7 +4650,7 @@ msgstr "Перетягніть, щоб перемістити точну точ
|
|||
msgid "Exact point selected. Drag the red pin or click the map to move it."
|
||||
msgstr "Точну точку вибрано. Перетягніть червоний маркер або натисніть на карту, щоб перемістити її."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:453
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:539
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} request in this map area"
|
||||
msgid_plural "%{count} requests in this map area"
|
||||
|
|
@ -4658,37 +4658,37 @@ msgstr[0] "%{count} заявка в цій області карти"
|
|||
msgstr[1] "%{count} заявки в цій області карти"
|
||||
msgstr[2] "%{count} заявок у цій області карти"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:438
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:430
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:502
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:516
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Area or city"
|
||||
msgstr "Район або місто"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:515
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:594
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:606
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose an area on the map"
|
||||
msgstr "Виберіть область на карті"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:350
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:436
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
|
||||
msgstr "Переглядайте приватні області та точні публічні місця зустрічі, вибрані авторами заявок."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:474
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:553
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:565
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "List"
|
||||
msgstr "Список"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:677
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Load more in this area"
|
||||
msgstr "Завантажити ще в цій області"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:5
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:504
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:573
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:585
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Map"
|
||||
msgstr "Карта"
|
||||
|
|
@ -4698,53 +4698,53 @@ msgstr "Карта"
|
|||
msgid "Map preview"
|
||||
msgstr "Попередній перегляд карти"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:537
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:606
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:618
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move or zoom the map to search somewhere else."
|
||||
msgstr "Перемістіть або масштабуйте карту, щоб шукати в іншому місці."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:471
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:461
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:540
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:552
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Move the map, then search the newly visible area."
|
||||
msgstr "Перемістіть карту, а потім виконайте пошук у новій видимій області."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:525
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:616
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open requests in this area"
|
||||
msgstr "У цій області немає відкритих заявок"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:608
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only requests from the selected map area will be loaded."
|
||||
msgstr "Буде завантажено лише заявки з вибраної області карти."
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:8
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:613
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:704
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open request"
|
||||
msgstr "Відкрити заявку"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:439
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:431
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:503
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:517
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Optional text search"
|
||||
msgstr "Необов’язковий текстовий пошук"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:592
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:683
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests without map coordinates appear only when their public area description matches the text search."
|
||||
msgstr "Заявки без координат на карті з’являються лише тоді, коли опис публічної області збігається з текстовим пошуком."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:475
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:465
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:544
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:556
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Results layout"
|
||||
msgstr "Розташування результатів"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:625
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:693
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:719
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Search this area"
|
||||
msgstr "Шукати в цій області"
|
||||
|
|
@ -4754,21 +4754,21 @@ msgstr "Шукати в цій області"
|
|||
msgid "Static preview"
|
||||
msgstr "Статичний перегляд"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:494
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:484
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:575
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Together"
|
||||
msgstr "Разом"
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:6
|
||||
#: lib/who_need_help_web/controllers/page_html/home.html.heex:160
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:609
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:681
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:355
|
||||
#: lib/who_need_help_web/live/activity_live/new.ex:414
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:546
|
||||
#: lib/who_need_help_web/live/notification_live.ex:602
|
||||
#: lib/who_need_help_web/live/notification_live.ex:621
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:612
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:703
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:485
|
||||
#: lib/who_need_help_web/live/request_live/new.ex:563
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1277
|
||||
|
|
@ -5158,7 +5158,7 @@ msgid "Nearby alert deleted."
|
|||
msgstr "Сповіщення поблизу видалено."
|
||||
|
||||
#: lib/who_need_help_web/live/notification_live.ex:456
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:376
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby requests"
|
||||
msgstr "Заявки поблизу"
|
||||
|
|
@ -5498,7 +5498,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
|
|||
msgstr "WebGL недоступний у цьому браузері, тому повна карта не може запуститися. Виберіть приклад нижче або спробуйте запустити карту ще раз."
|
||||
|
||||
#: lib/who_need_help_web/components/layouts/root.html.heex:7
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:610
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:682
|
||||
#: lib/who_need_help_web/live/activity_live/show.ex:547
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1278
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -6063,7 +6063,7 @@ msgstr "Тепер ваш відгук видно"
|
|||
msgid "Delivery settings"
|
||||
msgstr "Налаштування доставки"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:569
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:660
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Expires"
|
||||
msgstr "Спливає"
|
||||
|
|
@ -6083,12 +6083,12 @@ msgstr "Сповіщення поруч"
|
|||
msgid "Notification sections"
|
||||
msgstr "Розділи сповіщень"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:605
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:674
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activities"
|
||||
msgstr "Активностей: %{count}"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} activity in this map area"
|
||||
msgid_plural "%{count} activities in this map area"
|
||||
|
|
@ -6096,17 +6096,17 @@ msgstr[0] "%{count} активність у цій області карти"
|
|||
msgstr[1] "%{count} активності в цій області карти"
|
||||
msgstr[2] "%{count} активностей у цій області карти"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:614
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:705
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "%{count} requests"
|
||||
msgstr "Запитів: %{count}"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:645
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:717
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
|
||||
msgstr "Створені вами активності й ті, до яких ви попросили приєднатися, з’являться тут. Після виходу активність зникне зі списку."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:393
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Activity views"
|
||||
msgstr "Розділи активностей"
|
||||
|
|
@ -6121,12 +6121,12 @@ msgstr "Оберіть категорію та зрозуміло опишіть
|
|||
msgid "Confirm the safety guidance before asking nearby people for help."
|
||||
msgstr "Підтвердьте правила безпеки перед публікацією запиту."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:659
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:753
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create request"
|
||||
msgstr "Створити запит"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:673
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:776
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Create your first request"
|
||||
msgstr "Створити перший запит"
|
||||
|
|
@ -6141,43 +6141,43 @@ msgstr "Що потрібно зробити"
|
|||
msgid "Location"
|
||||
msgstr "Місце"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:399
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:463
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My activities"
|
||||
msgstr "Мої активності"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:377
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:653
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:463
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:747
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "My requests"
|
||||
msgstr "Мої запити"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:398
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nearby activities"
|
||||
msgstr "Активності поруч"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:643
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:715
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No activities yet"
|
||||
msgstr "Активностей поки немає"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No open activities in this area"
|
||||
msgstr "У цій області немає відкритих активностей"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:668
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:771
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No requests yet"
|
||||
msgstr "Запитів поки немає"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:527
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:596
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Only activities from the selected map area will be loaded."
|
||||
msgstr "Буде завантажено лише активності з вибраної області карти."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:604
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:673
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open activity"
|
||||
msgstr "Відкрити активність"
|
||||
|
|
@ -6187,12 +6187,12 @@ msgstr "Відкрити активність"
|
|||
msgid "Request form steps"
|
||||
msgstr "Кроки створення запиту"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:371
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:457
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Request views"
|
||||
msgstr "Розділи запитів"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:670
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:773
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Requests you publish or accept will appear here."
|
||||
msgstr "Тут з’являться запити, які ви створили або прийняли."
|
||||
|
|
@ -6217,7 +6217,7 @@ msgstr "Укажіть терміновість і час закриття за
|
|||
msgid "Share only as much location detail as helpers need."
|
||||
msgstr "Покажіть помічникам лише необхідну інформацію про місце."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:607
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:679
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The map is unavailable in this browser. Activity details remain usable."
|
||||
msgstr "Карта недоступна в цьому браузері. Інформація про активності залишається доступною."
|
||||
|
|
@ -6227,7 +6227,7 @@ msgstr "Карта недоступна в цьому браузері. Інфо
|
|||
msgid "Timing"
|
||||
msgstr "Час"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:655
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:749
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Track requests you created and requests you accepted as a helper."
|
||||
msgstr "Стежте за створеними запитами й тими, які ви прийняли як помічник."
|
||||
|
|
@ -6888,11 +6888,32 @@ msgstr "Що мають бачити люди до схвалення?"
|
|||
msgid "Choose the meeting point on the map or use your current location."
|
||||
msgstr "Оберіть місце зустрічі на карті або скористайтеся своїм поточним місцезнаходженням."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:458
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:517
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:635
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:448
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:507
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:522
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:586
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:707
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:534
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:598
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:763
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Loading results…"
|
||||
msgstr "Завантажуємо результати…"
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:676
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching activities are listed beside the map."
|
||||
msgstr "Досягнуто максимального масштабу. Відповідні заходи перелічено поруч із картою."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:707
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Maximum zoom reached. Matching requests are listed beside the map."
|
||||
msgstr "Досягнуто максимального масштабу. Відповідні заявки перелічено поруч із картою."
|
||||
|
||||
#: lib/who_need_help_web/live/activity_live/index.ex:535
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching activities with map locations"
|
||||
msgstr "із %{count} відповідних заходів із місцезнаходженням на карті"
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/index.ex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "of %{count} matching requests with map locations"
|
||||
msgstr "із %{count} відповідних заявок із місцезнаходженням на карті"
|
||||
|
|
|
|||
|
|
@ -448,6 +448,11 @@ defmodule WhoNeedHelp.ActivitiesTest do
|
|||
|
||||
cluster = Enum.find(items, &match?(%{type: "cluster", count: 3}, &1))
|
||||
assert cluster
|
||||
assert cluster.point_count == 3
|
||||
assert cluster.hierarchy_level == 11
|
||||
assert String.starts_with?(cluster.cluster_id, "activity:11:")
|
||||
assert String.starts_with?(cluster.parent_cluster_id, "activity:10:")
|
||||
assert cluster.expansion_zoom > 9
|
||||
assert_in_delta cluster.latitude, 50.45, 0.000_001
|
||||
assert_in_delta cluster.longitude, 30.52, 0.000_001
|
||||
assert_in_delta cluster.bounds.west, 30.52, 0.000_001
|
||||
|
|
@ -456,9 +461,33 @@ defmodule WhoNeedHelp.ActivitiesTest do
|
|||
assert_in_delta cluster.bounds.north, 50.45, 0.000_001
|
||||
|
||||
assert Enum.any?(items, fn
|
||||
%{type: "activity", id: id} -> id == sparse.id
|
||||
_item -> false
|
||||
%{
|
||||
type: "activity",
|
||||
id: id,
|
||||
hierarchy_level: 11,
|
||||
parent_cluster_id: "activity:10:" <> _
|
||||
} ->
|
||||
id == sparse.id
|
||||
|
||||
_item ->
|
||||
false
|
||||
end)
|
||||
|
||||
world_items =
|
||||
Activities.map_discovery_items(
|
||||
context.outsider_scope,
|
||||
%{},
|
||||
viewport!(-179.999, -85.0, 179.999, 85.0, 0)
|
||||
)
|
||||
|
||||
represented_count =
|
||||
Enum.reduce(world_items, 0, fn
|
||||
%{type: "cluster", count: count}, total -> total + count
|
||||
_point, total -> total + 1
|
||||
end)
|
||||
|
||||
assert represented_count ==
|
||||
Activities.count_map_discovery_items(context.outsider_scope, %{})
|
||||
end
|
||||
|
||||
test "scale fixture activities keep distinct synthetic public map coordinates", context do
|
||||
|
|
|
|||
43
test/who_need_help/help/discovery_cluster_test.exs
Normal file
43
test/who_need_help/help/discovery_cluster_test.exs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
defmodule WhoNeedHelp.Help.DiscoveryClusterTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias WhoNeedHelp.Help.DiscoveryCluster
|
||||
|
||||
test "map zooms select stable hierarchy levels" do
|
||||
assert DiscoveryCluster.level_for_zoom(0) == 2
|
||||
assert DiscoveryCluster.level_for_zoom(5.49) == 7
|
||||
assert DiscoveryCluster.level_for_zoom(5.99) == 7
|
||||
assert DiscoveryCluster.level_for_zoom(6) == 8
|
||||
assert DiscoveryCluster.level_for_zoom(22) == 24
|
||||
|
||||
assert_in_delta(
|
||||
DiscoveryCluster.cell_size_meters(8),
|
||||
DiscoveryCluster.cell_size_meters(7) / 2,
|
||||
0.000_001
|
||||
)
|
||||
end
|
||||
|
||||
test "cluster ids and parents form a deterministic quadtree" do
|
||||
assert DiscoveryCluster.cluster_id(:request, 8, 123, 456) == "request:8:123:456"
|
||||
assert DiscoveryCluster.parent_id(:request, 8, 123, 456) == "request:7:61:228"
|
||||
assert DiscoveryCluster.parent_id(:activity, 0, 0, 0) == nil
|
||||
end
|
||||
|
||||
test "expansion zoom is the first child level that separates member bounds" do
|
||||
level = 7
|
||||
cell = DiscoveryCluster.cell_size_meters(level)
|
||||
west = -DiscoveryCluster.world_half_meters() + cell * 10 + cell * 0.1
|
||||
south = -DiscoveryCluster.world_half_meters() + cell * 20 + cell * 0.1
|
||||
|
||||
assert DiscoveryCluster.expansion_zoom(
|
||||
level,
|
||||
west,
|
||||
south,
|
||||
west + cell * 0.6,
|
||||
south + cell * 0.6
|
||||
) == 6
|
||||
|
||||
assert DiscoveryCluster.expansion_zoom(level, west, south, west, south) == 22
|
||||
assert DiscoveryCluster.expansion_zoom(24, west, south, west, south) == 22
|
||||
end
|
||||
end
|
||||
|
|
@ -109,47 +109,6 @@ defmodule WhoNeedHelp.Help.DiscoveryViewportTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "derives cluster detail from both geographic bounds and rendered pixels" do
|
||||
{:ok, city} =
|
||||
DiscoveryViewport.cast(%{
|
||||
west: 30,
|
||||
south: 50,
|
||||
east: 31,
|
||||
north: 51,
|
||||
zoom: 9,
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
{:ok, neighborhood} =
|
||||
DiscoveryViewport.cast(%{
|
||||
west: 30.4,
|
||||
south: 50.4,
|
||||
east: 30.6,
|
||||
north: 50.6,
|
||||
zoom: 13,
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
{:ok, wide_canvas} =
|
||||
DiscoveryViewport.cast(%{
|
||||
west: 30,
|
||||
south: 50,
|
||||
east: 31,
|
||||
north: 51,
|
||||
zoom: 9,
|
||||
width: 1600,
|
||||
height: 1200
|
||||
})
|
||||
|
||||
assert DiscoveryViewport.cluster_cell_meters(city) >
|
||||
DiscoveryViewport.cluster_cell_meters(neighborhood)
|
||||
|
||||
assert DiscoveryViewport.cluster_cell_meters(city) >
|
||||
DiscoveryViewport.cluster_cell_meters(wide_canvas)
|
||||
end
|
||||
|
||||
test "serializes stable URL parameters and compares visible areas" do
|
||||
{:ok, viewport} =
|
||||
DiscoveryViewport.cast(%{
|
||||
|
|
|
|||
|
|
@ -564,6 +564,11 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
|
|||
|
||||
cluster = Enum.find(items, &match?(%{type: "cluster", count: 3}, &1))
|
||||
assert cluster
|
||||
assert cluster.point_count == 3
|
||||
assert cluster.hierarchy_level == 11
|
||||
assert String.starts_with?(cluster.cluster_id, "request:11:")
|
||||
assert String.starts_with?(cluster.parent_cluster_id, "request:10:")
|
||||
assert cluster.expansion_zoom > viewport.zoom
|
||||
assert_in_delta cluster.latitude, 50.4501, 0.000_001
|
||||
assert_in_delta cluster.longitude, 30.52345, 0.000_001
|
||||
assert_in_delta cluster.bounds.west, 30.5234, 0.000_001
|
||||
|
|
@ -572,8 +577,17 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
|
|||
assert_in_delta cluster.bounds.north, 50.4501, 0.000_001
|
||||
|
||||
assert Enum.any?(items, fn
|
||||
%{type: "request", id: id, exact: true} -> id == sparse_request.id
|
||||
_item -> false
|
||||
%{
|
||||
type: "request",
|
||||
id: id,
|
||||
exact: true,
|
||||
hierarchy_level: 11,
|
||||
parent_cluster_id: "request:10:" <> _
|
||||
} ->
|
||||
id == sparse_request.id
|
||||
|
||||
_item ->
|
||||
false
|
||||
end)
|
||||
|
||||
dense_ids = MapSet.new(dense_requests, & &1.id)
|
||||
|
|
@ -582,6 +596,21 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
|
|||
%{type: "request", id: id} -> MapSet.member?(dense_ids, id)
|
||||
_item -> false
|
||||
end)
|
||||
|
||||
world_items =
|
||||
Help.map_discovery_items(
|
||||
context.helper_scope,
|
||||
%{},
|
||||
viewport!(-179.999, -85.0, 179.999, 85.0, 0)
|
||||
)
|
||||
|
||||
represented_count =
|
||||
Enum.reduce(world_items, 0, fn
|
||||
%{type: "cluster", count: count}, total -> total + count
|
||||
_point, total -> total + 1
|
||||
end)
|
||||
|
||||
assert represented_count == Help.count_map_discovery_items(context.helper_scope, %{})
|
||||
end
|
||||
|
||||
test "map discovery does not expose an exact-for-match coordinate", context do
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user