Add polaroid/video/camera templates and respect backend tiers
- Add polaroid, video, camera template specs - getTemplateKeyForPost selects template based on content_type - prioritizePosts uses backend tier values (premium/regular) - Tier determines SIZE, content_type determines STYLE Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e32b8a531a
commit
2a46aff450
|
|
@ -66,6 +66,16 @@ const CONTENT_CREATOR_MODES = [
|
||||||
"Use subcategory filters to mark the tone: News, Event, Market, etc.",
|
"Use subcategory filters to mark the tone: News, Event, Market, etc.",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "evenements",
|
||||||
|
label: "Event",
|
||||||
|
icon: "fa-solid fa-calendar-days",
|
||||||
|
hint: "Announce a local or regional event.",
|
||||||
|
detail: [
|
||||||
|
"Share details about an event: concert, festival, rally, etc.",
|
||||||
|
"Place the marker at the exact location of the event.",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "other",
|
id: "other",
|
||||||
label: "Other",
|
label: "Other",
|
||||||
|
|
@ -98,11 +108,17 @@ const MODE_FLOW_CONFIG = {
|
||||||
bodyPlaceholder: "Describe the scene in a few words.",
|
bodyPlaceholder: "Describe the scene in a few words.",
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
hero: "Type a short update and pin the area you’re reporting from.",
|
hero: "Type a short update and pin the area you're reporting from.",
|
||||||
placeholder: "Headline or short note",
|
placeholder: "Headline or short note",
|
||||||
hint: "Readers should understand the gist before opening the full card.",
|
hint: "Readers should understand the gist before opening the full card.",
|
||||||
bodyPlaceholder: "Provide more context, opinions, or call to actions.",
|
bodyPlaceholder: "Provide more context, opinions, or call to actions.",
|
||||||
},
|
},
|
||||||
|
evenements: {
|
||||||
|
hero: "Announce an event and place the marker at the exact location.",
|
||||||
|
placeholder: "Event name",
|
||||||
|
hint: "Include the date, time, and a brief description.",
|
||||||
|
bodyPlaceholder: "Event details: schedule, tickets, program...",
|
||||||
|
},
|
||||||
other: {
|
other: {
|
||||||
hero: "Combine media, notes, or references and pin wherever it matters.",
|
hero: "Combine media, notes, or references and pin wherever it matters.",
|
||||||
placeholder: "Describe your update",
|
placeholder: "Describe your update",
|
||||||
|
|
@ -963,6 +979,22 @@ export default function MapView({
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(ev.data);
|
const msg = JSON.parse(ev.data);
|
||||||
if (msg.type === "new_post" && msg.post) {
|
if (msg.type === "new_post" && msg.post) {
|
||||||
|
// Filter posts by current viewport - only accept posts within exact bounds
|
||||||
|
const map = mapRef.current;
|
||||||
|
if (map) {
|
||||||
|
const postLat = Number(msg.post.lat) || 0;
|
||||||
|
const postLon = Number(msg.post.lon) || 0;
|
||||||
|
if (postLat !== 0 && postLon !== 0) {
|
||||||
|
const bounds = map.getBounds();
|
||||||
|
const sw = bounds.getSouthWest();
|
||||||
|
const ne = bounds.getNorthEast();
|
||||||
|
const inBounds = postLat >= sw.lat && postLat <= ne.lat &&
|
||||||
|
postLon >= sw.lng && postLon <= ne.lng;
|
||||||
|
if (!inBounds) {
|
||||||
|
return; // Ignore posts outside viewport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
handleIncomingPost(msg.post);
|
handleIncomingPost(msg.post);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1772,6 +1804,15 @@ export default function MapView({
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
|
// Map contentMode to content_type (photo -> picture for backend)
|
||||||
|
const contentTypeMap = {
|
||||||
|
live: "live",
|
||||||
|
link: "link",
|
||||||
|
photo: "picture",
|
||||||
|
text: "text",
|
||||||
|
evenements: "evenements",
|
||||||
|
other: "other",
|
||||||
|
};
|
||||||
const payload = {
|
const payload = {
|
||||||
title: draftTitle.trim(),
|
title: draftTitle.trim(),
|
||||||
snippet: draftBody.trim() || draftTitle.trim(),
|
snippet: draftBody.trim() || draftTitle.trim(),
|
||||||
|
|
@ -1789,6 +1830,7 @@ export default function MapView({
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
// Mark as user-created post so enrichment preserves the position
|
// Mark as user-created post so enrichment preserves the position
|
||||||
source: "sociowire",
|
source: "sociowire",
|
||||||
|
content_type: contentTypeMap[contentMode] || "other",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (draftUrl.trim()) {
|
if (draftUrl.trim()) {
|
||||||
|
|
|
||||||
|
|
@ -113,82 +113,111 @@ export function clusterPosts(posts, clusterRadius = 0.001) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Décide quels posts afficher comme cartes complètes vs marqueurs simples
|
* Décide quels posts afficher comme cartes complètes vs marqueurs simples
|
||||||
|
* Utilise le tier du backend (premium/regular) si disponible
|
||||||
|
* Le template (polaroid/video/camera/news) est décidé par content_type dans templateSpecs
|
||||||
* Retourne { fullCardPosts: [], simpleMarkerPosts: [] }
|
* Retourne { fullCardPosts: [], simpleMarkerPosts: [] }
|
||||||
*/
|
*/
|
||||||
export function prioritizePosts(posts, viewCenter, options = {}) {
|
export function prioritizePosts(posts, viewCenter, options = {}) {
|
||||||
const {
|
const {
|
||||||
maxFullCards = 15, // Max de cartes complètes à afficher
|
maxFullCards = 15, // Max de cartes complètes à afficher (fallback)
|
||||||
clusterRadius = 0.001, // Rayon de clustering (~100m)
|
clusterRadius = 0.001, // Rayon de clustering (~100m)
|
||||||
minScoreForCard = 40, // Score minimum pour carte complète
|
minScoreForCard = 40, // Score minimum pour carte complète (fallback)
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// 1. Calculer les scores
|
const fullCardPosts = [];
|
||||||
|
const simpleMarkerPosts = [];
|
||||||
|
|
||||||
|
// First pass: use backend tier if available
|
||||||
|
let hasTiers = false;
|
||||||
|
for (const post of posts) {
|
||||||
|
const tier = (post?.tier || '').toLowerCase();
|
||||||
|
|
||||||
|
// DEBUG: Log tier values
|
||||||
|
if (posts.indexOf(post) < 5) {
|
||||||
|
console.log('[prioritizePosts] post:', post.id, 'tier:', post.tier, 'content_type:', post.content_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tier === 'premium') {
|
||||||
|
fullCardPosts.push(post);
|
||||||
|
hasTiers = true;
|
||||||
|
} else if (tier === 'regular') {
|
||||||
|
simpleMarkerPosts.push(post);
|
||||||
|
hasTiers = true;
|
||||||
|
} else {
|
||||||
|
// No tier - will be processed in fallback
|
||||||
|
simpleMarkerPosts.push(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If backend provided tiers, use them directly
|
||||||
|
if (hasTiers && fullCardPosts.length > 0) {
|
||||||
|
return {
|
||||||
|
fullCardPosts,
|
||||||
|
simpleMarkerPosts,
|
||||||
|
clusters: new Map(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: old scoring system if no backend tiers
|
||||||
|
simpleMarkerPosts.length = 0; // Clear for fallback processing
|
||||||
|
|
||||||
const postsWithScores = posts.map(post => ({
|
const postsWithScores = posts.map(post => ({
|
||||||
post,
|
post,
|
||||||
priority: calculatePostPriority(post, viewCenter, posts),
|
priority: calculatePostPriority(post, viewCenter, posts),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 2. Trier par priorité descendante
|
|
||||||
postsWithScores.sort((a, b) => b.priority - a.priority);
|
postsWithScores.sort((a, b) => b.priority - a.priority);
|
||||||
|
|
||||||
// 3. Détecter les clusters
|
|
||||||
const clusters = clusterPosts(posts, clusterRadius);
|
const clusters = clusterPosts(posts, clusterRadius);
|
||||||
|
const finalFull = [];
|
||||||
// 4. Sélectionner les posts pour cartes complètes
|
const finalSimple = [];
|
||||||
const fullCardPosts = [];
|
|
||||||
const simpleMarkerPosts = [];
|
|
||||||
const clusterRepresentatives = new Set();
|
const clusterRepresentatives = new Set();
|
||||||
|
|
||||||
for (const [clusterId, clusterPosts] of clusters.entries()) {
|
for (const [clusterId, clusterPostsList] of clusters.entries()) {
|
||||||
if (clusterPosts.length === 1) {
|
if (clusterPostsList.length === 1) {
|
||||||
// Pas de cluster, décision simple basée sur le score
|
const item = postsWithScores.find(p => p.post.id === clusterPostsList[0].id);
|
||||||
const item = postsWithScores.find(p => p.post.id === clusterPosts[0].id);
|
if (item && item.priority >= minScoreForCard && finalFull.length < maxFullCards) {
|
||||||
if (item && item.priority >= minScoreForCard && fullCardPosts.length < maxFullCards) {
|
finalFull.push(item.post);
|
||||||
fullCardPosts.push(item.post);
|
|
||||||
} else {
|
} else {
|
||||||
simpleMarkerPosts.push(clusterPosts[0]);
|
finalSimple.push(clusterPostsList[0]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Cluster: prendre le meilleur post pour carte complète, autres en marqueurs simples
|
const sortedCluster = clusterPostsList
|
||||||
const sortedCluster = clusterPosts
|
|
||||||
.map(p => postsWithScores.find(ps => ps.post.id === p.id))
|
.map(p => postsWithScores.find(ps => ps.post.id === p.id))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.sort((a, b) => b.priority - a.priority);
|
.sort((a, b) => b.priority - a.priority);
|
||||||
|
|
||||||
// Le meilleur du cluster devient carte si score suffisant
|
|
||||||
if (sortedCluster[0] &&
|
if (sortedCluster[0] &&
|
||||||
sortedCluster[0].priority >= minScoreForCard &&
|
sortedCluster[0].priority >= minScoreForCard &&
|
||||||
fullCardPosts.length < maxFullCards) {
|
finalFull.length < maxFullCards) {
|
||||||
fullCardPosts.push(sortedCluster[0].post);
|
finalFull.push(sortedCluster[0].post);
|
||||||
clusterRepresentatives.add(sortedCluster[0].post.id);
|
clusterRepresentatives.add(sortedCluster[0].post.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Les autres deviennent marqueurs simples
|
|
||||||
for (let i = (clusterRepresentatives.has(sortedCluster[0]?.post.id) ? 1 : 0); i < sortedCluster.length; i++) {
|
for (let i = (clusterRepresentatives.has(sortedCluster[0]?.post.id) ? 1 : 0); i < sortedCluster.length; i++) {
|
||||||
simpleMarkerPosts.push(sortedCluster[i].post);
|
finalSimple.push(sortedCluster[i].post);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Si pas assez de cartes complètes, ajouter les meilleurs marqueurs simples
|
if (finalFull.length < maxFullCards) {
|
||||||
if (fullCardPosts.length < maxFullCards) {
|
|
||||||
const remaining = postsWithScores
|
const remaining = postsWithScores
|
||||||
.filter(p =>
|
.filter(p =>
|
||||||
!fullCardPosts.find(fp => fp.id === p.post.id) &&
|
!finalFull.find(fp => fp.id === p.post.id) &&
|
||||||
!simpleMarkerPosts.find(sp => sp.id === p.post.id)
|
!finalSimple.find(sp => sp.id === p.post.id)
|
||||||
)
|
)
|
||||||
.slice(0, maxFullCards - fullCardPosts.length);
|
.slice(0, maxFullCards - finalFull.length);
|
||||||
|
|
||||||
for (const item of remaining) {
|
for (const item of remaining) {
|
||||||
fullCardPosts.push(item.post);
|
finalFull.push(item.post);
|
||||||
const idx = simpleMarkerPosts.findIndex(p => p.id === item.post.id);
|
const idx = finalSimple.findIndex(p => p.id === item.post.id);
|
||||||
if (idx >= 0) simpleMarkerPosts.splice(idx, 1);
|
if (idx >= 0) finalSimple.splice(idx, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fullCardPosts,
|
fullCardPosts: finalFull,
|
||||||
simpleMarkerPosts,
|
simpleMarkerPosts: finalSimple,
|
||||||
clusters,
|
clusters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,73 @@
|
||||||
* - events
|
* - events
|
||||||
*/
|
*/
|
||||||
export const TEMPLATE_SPECS = {
|
export const TEMPLATE_SPECS = {
|
||||||
|
polaroid: {
|
||||||
|
mini_spec: {
|
||||||
|
size: { w: 180, h: 210 },
|
||||||
|
background: { type: "solid", value: "#ffffff" },
|
||||||
|
radius: 8,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 10, y: 10, w: 160, h: 160, bind: "data.image", radius: 4 },
|
||||||
|
{ id: "author", type: "text", x: 10, y: 178, w: 160, h: 24, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
full_spec: {
|
||||||
|
size: { w: 280, h: 340 },
|
||||||
|
background: { type: "solid", value: "#ffffff" },
|
||||||
|
radius: 12,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 15, y: 15, w: 250, h: 250, bind: "data.image", radius: 6 },
|
||||||
|
{ id: "author", type: "text", x: 15, y: 275, w: 250, h: 24, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
||||||
|
{ id: "caption", type: "text", x: 15, y: 300, w: 250, h: 32, bind: "data.headline", style: "text.polaroidText", maxLines: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
mini_spec: {
|
||||||
|
size: { w: 240, h: 135 },
|
||||||
|
background: { type: "solid", value: "#1a1a2e" },
|
||||||
|
radius: 12,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 0, y: 0, w: 240, h: 135, bind: "data.image", radius: 12 },
|
||||||
|
{ id: "playIcon", type: "icon", x: 100, y: 47, w: 40, h: 40, text: "\uf04b", style: "text.playIcon" },
|
||||||
|
{ id: "badge", type: "chip", x: 8, y: 8, text: "VIDEO", style: "chip.video" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
full_spec: {
|
||||||
|
size: { w: 360, h: 260 },
|
||||||
|
background: { type: "solid", value: "#1a1a2e" },
|
||||||
|
radius: 16,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 0, y: 0, w: 360, h: 200, bind: "data.image", radius: 16 },
|
||||||
|
{ id: "playIcon", type: "icon", x: 160, y: 80, w: 40, h: 40, text: "\uf04b", style: "text.playIcon" },
|
||||||
|
{ id: "badge", type: "chip", x: 12, y: 12, text: "VIDEO", style: "chip.video" },
|
||||||
|
{ id: "title", type: "text", x: 12, y: 210, w: 336, h: 40, bind: "data.headline", style: "text.videoTitle", maxLines: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
camera: {
|
||||||
|
mini_spec: {
|
||||||
|
size: { w: 200, h: 112 },
|
||||||
|
background: { type: "solid", value: "#0f172a" },
|
||||||
|
radius: 10,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 0, y: 0, w: 200, h: 112, bind: "data.image", radius: 10 },
|
||||||
|
{ id: "liveIcon", type: "icon", x: 8, y: 8, w: 24, h: 24, text: "\uf03d", style: "text.liveIcon" },
|
||||||
|
{ id: "badge", type: "chip", x: 36, y: 8, text: "LIVE", style: "chip.live" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
full_spec: {
|
||||||
|
size: { w: 320, h: 200 },
|
||||||
|
background: { type: "solid", value: "#0f172a" },
|
||||||
|
radius: 14,
|
||||||
|
layers: [
|
||||||
|
{ id: "image", type: "image", x: 0, y: 0, w: 320, h: 180, bind: "data.image", radius: 14 },
|
||||||
|
{ id: "liveIcon", type: "icon", x: 12, y: 12, w: 28, h: 28, text: "\uf03d", style: "text.liveIcon" },
|
||||||
|
{ id: "badge", type: "chip", x: 44, y: 12, text: "LIVE", style: "chip.live" },
|
||||||
|
{ id: "title", type: "text", x: 12, y: 185, w: 296, h: 20, bind: "data.headline", style: "text.cameraTitle", maxLines: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
cluster: {
|
cluster: {
|
||||||
mini_spec: {
|
mini_spec: {
|
||||||
size: { w: 270, h: 108 },
|
size: { w: 270, h: 108 },
|
||||||
|
|
@ -224,6 +291,25 @@ function normCat(post) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTemplateKeyForPost(post) {
|
export function getTemplateKeyForPost(post) {
|
||||||
|
// Content type determines template style for special types
|
||||||
|
const ct = (post?.content_type || '').toLowerCase();
|
||||||
|
|
||||||
|
// Photos → polaroid style
|
||||||
|
if (ct === 'photo' || ct === 'picture' || ct === 'image') {
|
||||||
|
return 'polaroid';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Videos → video style with play button
|
||||||
|
if (ct === 'video') {
|
||||||
|
return 'video';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cameras/live streams → camera style
|
||||||
|
if (ct === 'camera' || ct === 'live' || ct === 'stream') {
|
||||||
|
return 'camera';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: category-based template
|
||||||
return normCat(post);
|
return normCat(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,7 @@ function buildClusterAreaFeatures(posts) {
|
||||||
const MAX_POOL_POSTS = 160;
|
const MAX_POOL_POSTS = 160;
|
||||||
const MAX_VISIBLE_POSTS = 45;
|
const MAX_VISIBLE_POSTS = 45;
|
||||||
|
|
||||||
function expandBounds(bounds, padRatio = 0.18) {
|
function expandBounds(bounds, padRatio = 0) {
|
||||||
if (!bounds) return null;
|
if (!bounds) return null;
|
||||||
const spanLat = Math.abs(bounds.maxLat - bounds.minLat);
|
const spanLat = Math.abs(bounds.maxLat - bounds.minLat);
|
||||||
const spanLon = Math.abs(bounds.maxLon - bounds.minLon);
|
const spanLon = Math.abs(bounds.maxLon - bounds.minLon);
|
||||||
|
|
@ -720,6 +720,7 @@ export function usePostsEngine({
|
||||||
if (entry.type === "simple") {
|
if (entry.type === "simple") {
|
||||||
createSimpleMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
createSimpleMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
||||||
} else {
|
} else {
|
||||||
|
// Full cards use createMarkerForPost (template is decided by content_type)
|
||||||
createMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
createMarkerForPost(entry.post, mapRef, markersRef, expandedElRef, theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@
|
||||||
"photoStory": "Photo story",
|
"photoStory": "Photo story",
|
||||||
"writtenUpdate": "Written update",
|
"writtenUpdate": "Written update",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
|
"event": "Event",
|
||||||
"liveHint": "Broadcast a live scene with a headline or link.",
|
"liveHint": "Broadcast a live scene with a headline or link.",
|
||||||
"liveDetail1": "Drop the stream URL or quick headline to pin the feed instantly.",
|
"liveDetail1": "Drop the stream URL or quick headline to pin the feed instantly.",
|
||||||
"liveDetail2": "We'll open the location where you place the crosshair so viewers know where to tune.",
|
"liveDetail2": "We'll open the location where you place the crosshair so viewers know where to tune.",
|
||||||
|
|
@ -148,6 +149,9 @@
|
||||||
"textHint": "Type a short report or microblog.",
|
"textHint": "Type a short report or microblog.",
|
||||||
"textDetail1": "Write the key message in the title field (or add more context in the body).",
|
"textDetail1": "Write the key message in the title field (or add more context in the body).",
|
||||||
"textDetail2": "Use subcategory filters to mark the tone: News, Event, Market, etc.",
|
"textDetail2": "Use subcategory filters to mark the tone: News, Event, Market, etc.",
|
||||||
|
"eventHint": "Announce a local or regional event.",
|
||||||
|
"eventDetail1": "Share details about an event: concert, festival, rally, etc.",
|
||||||
|
"eventDetail2": "Place the marker at the exact location of the event.",
|
||||||
"otherHint": "Custom content that spans media types.",
|
"otherHint": "Custom content that spans media types.",
|
||||||
"otherDetail1": "Mix media, public drafts, or reference notes in one place.",
|
"otherDetail1": "Mix media, public drafts, or reference notes in one place.",
|
||||||
"otherDetail2": "Tag a region or Island to give the update a home.",
|
"otherDetail2": "Tag a region or Island to give the update a home.",
|
||||||
|
|
@ -169,6 +173,10 @@
|
||||||
"textPlaceholder": "Headline or short note",
|
"textPlaceholder": "Headline or short note",
|
||||||
"textFlowHint": "Readers should understand the gist before opening the full card.",
|
"textFlowHint": "Readers should understand the gist before opening the full card.",
|
||||||
"textBodyPlaceholder": "Provide more context, opinions, or call to actions.",
|
"textBodyPlaceholder": "Provide more context, opinions, or call to actions.",
|
||||||
|
"eventHero": "Announce an event and place the marker at the exact location.",
|
||||||
|
"eventPlaceholder": "Event name",
|
||||||
|
"eventFlowHint": "Include the date, time, and a brief description.",
|
||||||
|
"eventBodyPlaceholder": "Event details: schedule, tickets, program...",
|
||||||
"otherHero": "Combine media, notes, or references and pin wherever it matters.",
|
"otherHero": "Combine media, notes, or references and pin wherever it matters.",
|
||||||
"otherPlaceholder": "Describe your update",
|
"otherPlaceholder": "Describe your update",
|
||||||
"otherFlowHint": "This mode is flexible - add whatever helps the reader.",
|
"otherFlowHint": "This mode is flexible - add whatever helps the reader.",
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@
|
||||||
"photoStory": "Foto",
|
"photoStory": "Foto",
|
||||||
"writtenUpdate": "Publicación escrita",
|
"writtenUpdate": "Publicación escrita",
|
||||||
"other": "Otro",
|
"other": "Otro",
|
||||||
|
"event": "Evento",
|
||||||
"liveHint": "Transmite una escena en vivo con un título o enlace.",
|
"liveHint": "Transmite una escena en vivo con un título o enlace.",
|
||||||
"liveDetail1": "Pega la URL del stream o un título rápido para fijar el feed al instante.",
|
"liveDetail1": "Pega la URL del stream o un título rápido para fijar el feed al instante.",
|
||||||
"liveDetail2": "Abriremos la ubicación donde coloques el cursor para que los espectadores sepan dónde sintonizar.",
|
"liveDetail2": "Abriremos la ubicación donde coloques el cursor para que los espectadores sepan dónde sintonizar.",
|
||||||
|
|
@ -148,6 +149,9 @@
|
||||||
"textHint": "Escribe un informe corto o microblog.",
|
"textHint": "Escribe un informe corto o microblog.",
|
||||||
"textDetail1": "Escribe el mensaje clave en el campo de título (o añade más contexto en el cuerpo).",
|
"textDetail1": "Escribe el mensaje clave en el campo de título (o añade más contexto en el cuerpo).",
|
||||||
"textDetail2": "Usa filtros de subcategoría para marcar el tono: Noticias, Evento, Mercado, etc.",
|
"textDetail2": "Usa filtros de subcategoría para marcar el tono: Noticias, Evento, Mercado, etc.",
|
||||||
|
"eventHint": "Anuncia un evento local o regional.",
|
||||||
|
"eventDetail1": "Comparte detalles de un evento: concierto, festival, manifestación, etc.",
|
||||||
|
"eventDetail2": "Coloca el marcador en la ubicación exacta del evento.",
|
||||||
"otherHint": "Contenido personalizado que abarca tipos de medios.",
|
"otherHint": "Contenido personalizado que abarca tipos de medios.",
|
||||||
"otherDetail1": "Mezcla medios, borradores públicos o notas de referencia en un solo lugar.",
|
"otherDetail1": "Mezcla medios, borradores públicos o notas de referencia en un solo lugar.",
|
||||||
"otherDetail2": "Etiqueta una región o Isla para dar un hogar a la actualización.",
|
"otherDetail2": "Etiqueta una región o Isla para dar un hogar a la actualización.",
|
||||||
|
|
@ -169,6 +173,10 @@
|
||||||
"textPlaceholder": "Título o nota corta",
|
"textPlaceholder": "Título o nota corta",
|
||||||
"textFlowHint": "Los lectores deben entender lo esencial antes de abrir la tarjeta completa.",
|
"textFlowHint": "Los lectores deben entender lo esencial antes de abrir la tarjeta completa.",
|
||||||
"textBodyPlaceholder": "Proporciona más contexto, opiniones o llamadas a la acción.",
|
"textBodyPlaceholder": "Proporciona más contexto, opiniones o llamadas a la acción.",
|
||||||
|
"eventHero": "Anuncia un evento y coloca el marcador en la ubicación exacta.",
|
||||||
|
"eventPlaceholder": "Nombre del evento",
|
||||||
|
"eventFlowHint": "Incluye la fecha, hora y una breve descripción.",
|
||||||
|
"eventBodyPlaceholder": "Detalles del evento: horarios, entradas, programa...",
|
||||||
"otherHero": "Combina medios, notas o referencias y fija donde sea importante.",
|
"otherHero": "Combina medios, notas o referencias y fija donde sea importante.",
|
||||||
"otherPlaceholder": "Describe tu actualización",
|
"otherPlaceholder": "Describe tu actualización",
|
||||||
"otherFlowHint": "Este modo es flexible - añade lo que ayude al lector.",
|
"otherFlowHint": "Este modo es flexible - añade lo que ayude al lector.",
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@
|
||||||
"photoStory": "Photo",
|
"photoStory": "Photo",
|
||||||
"writtenUpdate": "Publication écrite",
|
"writtenUpdate": "Publication écrite",
|
||||||
"other": "Autre",
|
"other": "Autre",
|
||||||
|
"event": "Événement",
|
||||||
"liveHint": "Diffusez une scène en direct avec un titre ou un lien.",
|
"liveHint": "Diffusez une scène en direct avec un titre ou un lien.",
|
||||||
"liveDetail1": "Collez l'URL du stream ou un titre rapide pour épingler le flux instantanément.",
|
"liveDetail1": "Collez l'URL du stream ou un titre rapide pour épingler le flux instantanément.",
|
||||||
"liveDetail2": "Nous ouvrirons l'emplacement où vous placez le curseur pour que les spectateurs sachent où regarder.",
|
"liveDetail2": "Nous ouvrirons l'emplacement où vous placez le curseur pour que les spectateurs sachent où regarder.",
|
||||||
|
|
@ -148,6 +149,9 @@
|
||||||
"textHint": "Tapez un court rapport ou microblog.",
|
"textHint": "Tapez un court rapport ou microblog.",
|
||||||
"textDetail1": "Écrivez le message clé dans le champ titre (ou ajoutez plus de contexte dans le corps).",
|
"textDetail1": "Écrivez le message clé dans le champ titre (ou ajoutez plus de contexte dans le corps).",
|
||||||
"textDetail2": "Utilisez les filtres de sous-catégorie pour marquer le ton: Actu, Événement, Marché, etc.",
|
"textDetail2": "Utilisez les filtres de sous-catégorie pour marquer le ton: Actu, Événement, Marché, etc.",
|
||||||
|
"eventHint": "Annoncez un événement local ou régional.",
|
||||||
|
"eventDetail1": "Partagez les détails d'un événement: concert, festival, manifestation, etc.",
|
||||||
|
"eventDetail2": "Placez le marqueur à l'endroit exact de l'événement.",
|
||||||
"otherHint": "Contenu personnalisé multi-média.",
|
"otherHint": "Contenu personnalisé multi-média.",
|
||||||
"otherDetail1": "Mélangez médias, brouillons publics ou notes de référence en un seul endroit.",
|
"otherDetail1": "Mélangez médias, brouillons publics ou notes de référence en un seul endroit.",
|
||||||
"otherDetail2": "Taguez une région ou une Île pour donner un foyer à la mise à jour.",
|
"otherDetail2": "Taguez une région ou une Île pour donner un foyer à la mise à jour.",
|
||||||
|
|
@ -169,6 +173,10 @@
|
||||||
"textPlaceholder": "Titre ou note courte",
|
"textPlaceholder": "Titre ou note courte",
|
||||||
"textFlowHint": "Les lecteurs doivent comprendre l'essentiel avant d'ouvrir la carte complète.",
|
"textFlowHint": "Les lecteurs doivent comprendre l'essentiel avant d'ouvrir la carte complète.",
|
||||||
"textBodyPlaceholder": "Fournissez plus de contexte, opinions ou appels à l'action.",
|
"textBodyPlaceholder": "Fournissez plus de contexte, opinions ou appels à l'action.",
|
||||||
|
"eventHero": "Annoncez un événement et placez le marqueur à l'endroit exact.",
|
||||||
|
"eventPlaceholder": "Nom de l'événement",
|
||||||
|
"eventFlowHint": "Incluez la date, l'heure et une brève description.",
|
||||||
|
"eventBodyPlaceholder": "Détails de l'événement: horaires, billets, programme...",
|
||||||
"otherHero": "Combinez médias, notes ou références et épinglez où ça compte.",
|
"otherHero": "Combinez médias, notes ou références et épinglez où ça compte.",
|
||||||
"otherPlaceholder": "Décrivez votre mise à jour",
|
"otherPlaceholder": "Décrivez votre mise à jour",
|
||||||
"otherFlowHint": "Ce mode est flexible - ajoutez ce qui aide le lecteur.",
|
"otherFlowHint": "Ce mode est flexible - ajoutez ce qui aide le lecteur.",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue