feat(ui): reecrire la politique de confidentialite et celle des cookies

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
This commit is contained in:
David 2026-09-07 21:40:58 +02:00
parent 5504f9c1ce
commit 5185dfb405
5 changed files with 638 additions and 212 deletions

View File

@ -20,6 +20,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { Link } from '@/i18n/navigation'; import { Link } from '@/i18n/navigation';
import { LandingHeader, LandingFooter } from '@/components/layout'; import { LandingHeader, LandingFooter } from '@/components/layout';
import { CONTROLLER } from '@/lib/legal/processing';
type RightKey = 'access' | 'rectification' | 'erasure' | 'portability'; type RightKey = 'access' | 'rectification' | 'erasure' | 'portability';
type PrincipleKey = 'minimization' | 'retention' | 'integrity' | 'transparency'; type PrincipleKey = 'minimization' | 'retention' | 'integrity' | 'transparency';
@ -43,6 +44,11 @@ const MEASURES: MeasureKey[] = ['technical', 'organizational'];
const MEASURE_ITEMS = ['item1', 'item2', 'item3', 'item4', 'item5'] as const; const MEASURE_ITEMS = ['item1', 'item2', 'item3', 'item4', 'item5'] as const;
const REGISTER_ITEMS = ['item1', 'item2', 'item3', 'item4', 'item5'] as const; const REGISTER_ITEMS = ['item1', 'item2', 'item3', 'item4', 'item5'] as const;
// Le lien de contact suit la realite : ecrire a une boite dpo@ qui
// n'existe pas laisserait une demande de droits sans destinataire.
const hasDpo = Boolean(CONTROLLER.dpo);
const contactAddress = CONTROLLER.dpo ?? CONTROLLER.privacyContact;
export default function CompliancePage() { export default function CompliancePage() {
const t = useTranslations('marketing.compliance'); const t = useTranslations('marketing.compliance');
const heroRef = useRef(null); const heroRef = useRef(null);
@ -120,10 +126,12 @@ export default function CompliancePage() {
<CheckCircle className="w-5 h-5 text-brand-green" /> <CheckCircle className="w-5 h-5 text-brand-green" />
<span className="text-white text-sm">{t('badges.compliant')}</span> <span className="text-white text-sm">{t('badges.compliant')}</span>
</div> </div>
{hasDpo && (
<div className="flex items-center space-x-2 bg-white/10 px-4 py-2 rounded-lg"> <div className="flex items-center space-x-2 bg-white/10 px-4 py-2 rounded-lg">
<UserCheck className="w-5 h-5 text-brand-green" /> <UserCheck className="w-5 h-5 text-brand-green" />
<span className="text-white text-sm">{t('badges.dpo')}</span> <span className="text-white text-sm">{t('badges.dpo')}</span>
</div> </div>
)}
</div> </div>
</motion.div> </motion.div>
</div> </div>
@ -197,10 +205,10 @@ export default function CompliancePage() {
{t('rightsCta.login')} {t('rightsCta.login')}
</Link> </Link>
<a <a
href="mailto:dpo@xpeditis.com" href={`mailto:${contactAddress}`}
className="px-6 py-3 bg-brand-navy text-white rounded-lg hover:bg-brand-navy/90 transition-colors font-medium" className="px-6 py-3 bg-brand-navy text-white rounded-lg hover:bg-brand-navy/90 transition-colors font-medium"
> >
{t('rightsCta.dpo')} {hasDpo ? t('rightsCta.dpo') : t('rightsCta.contact')}
</a> </a>
</div> </div>
</motion.div> </motion.div>
@ -279,7 +287,9 @@ export default function CompliancePage() {
> >
<h3 className="text-xl font-bold text-white mb-6">{t(`measures.${key}.title`)}</h3> <h3 className="text-xl font-bold text-white mb-6">{t(`measures.${key}.title`)}</h3>
<ul className="space-y-4"> <ul className="space-y-4">
{MEASURE_ITEMS.map(itemKey => ( {MEASURE_ITEMS.filter(
itemKey => hasDpo || !(key === 'organizational' && itemKey === 'item1')
).map(itemKey => (
<li key={itemKey} className="flex items-center space-x-3 text-white/80"> <li key={itemKey} className="flex items-center space-x-3 text-white/80">
<CheckCircle className="w-5 h-5 text-brand-turquoise flex-shrink-0" /> <CheckCircle className="w-5 h-5 text-brand-turquoise flex-shrink-0" />
<span>{t(`measures.${key}.${itemKey}` as any)}</span> <span>{t(`measures.${key}.${itemKey}` as any)}</span>
@ -334,15 +344,19 @@ export default function CompliancePage() {
className="bg-gradient-to-br from-brand-navy to-brand-navy/95 p-10 rounded-3xl text-center" className="bg-gradient-to-br from-brand-navy to-brand-navy/95 p-10 rounded-3xl text-center"
> >
<UserCheck className="w-12 h-12 text-brand-turquoise mx-auto mb-4" /> <UserCheck className="w-12 h-12 text-brand-turquoise mx-auto mb-4" />
<h3 className="text-2xl font-bold text-white mb-4">{t('dpo.title')}</h3> <h3 className="text-2xl font-bold text-white mb-4">
<p className="text-white/80 mb-6 max-w-2xl mx-auto">{t('dpo.body')}</p> {hasDpo ? t('dpo.title') : t('contact.title')}
</h3>
<p className="text-white/80 mb-6 max-w-2xl mx-auto">
{hasDpo ? t('dpo.body') : t('contact.body')}
</p>
<div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4"> <div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4">
<a <a
href="mailto:dpo@xpeditis.com" href={`mailto:${contactAddress}`}
className="inline-flex items-center space-x-2 px-6 py-3 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-colors font-medium" className="inline-flex items-center space-x-2 px-6 py-3 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-colors font-medium"
> >
<Mail className="w-5 h-5" /> <Mail className="w-5 h-5" />
<span>dpo@xpeditis.com</span> <span>{contactAddress}</span>
</a> </a>
<Link <Link
href="/privacy" href="/privacy"

View File

@ -14,63 +14,32 @@ import {
type LucideIcon, type LucideIcon,
} from 'lucide-react'; } from 'lucide-react';
import { LandingHeader, LandingFooter } from '@/components/layout'; import { LandingHeader, LandingFooter } from '@/components/layout';
import { ESSENTIAL_STORAGE, FUNCTIONAL_STORAGE, type StoredItem } from '@/lib/legal/processing';
type CookieTypeKey = 'essential' | 'analytics' | 'marketing' | 'functional'; type CookieTypeKey = 'essential' | 'analytics' | 'marketing' | 'functional';
interface CookieRow {
name: string;
purposeKey: string;
durationKey: string;
}
interface CookieTypeConfig { interface CookieTypeConfig {
key: CookieTypeKey; key: CookieTypeKey;
icon: LucideIcon; icon: LucideIcon;
required: boolean; required: boolean;
cookies: CookieRow[]; items: readonly StoredItem[];
} }
/**
* Inventaire reel de ce que le site depose.
*
* La version precedente listait Google Analytics (`_ga`, `_gid`, `_gat`),
* Facebook Pixel (`_fbp`), LinkedIn (`li_fat_id`) et HubSpot (`hubspotutk`).
* Aucun de ces scripts n'est charge par l'application : annoncer des traceurs
* qui n'existent pas est aussi inexact que d'en dissimuler, et rendait le
* consentement demande sans objet. Les categories analytiques et marketing
* restent affichees, vides, pour que leur absence soit une information.
*/
const COOKIE_TYPES: CookieTypeConfig[] = [ const COOKIE_TYPES: CookieTypeConfig[] = [
{ { key: 'essential', icon: Shield, required: true, items: ESSENTIAL_STORAGE },
key: 'essential', { key: 'functional', icon: Settings, required: false, items: FUNCTIONAL_STORAGE },
icon: Shield, { key: 'analytics', icon: BarChart3, required: false, items: [] },
required: true, { key: 'marketing', icon: Target, required: false, items: [] },
cookies: [
{ name: 'session_id', purposeKey: 'session_id', durationKey: 'session' },
{ name: 'csrf_token', purposeKey: 'csrf_token', durationKey: 'session' },
{ name: 'cookie_consent', purposeKey: 'cookie_consent', durationKey: 'year1' },
],
},
{
key: 'analytics',
icon: BarChart3,
required: false,
cookies: [
{ name: '_ga', purposeKey: '_ga', durationKey: 'years2' },
{ name: '_gid', purposeKey: '_gid', durationKey: 'hours24' },
{ name: '_gat', purposeKey: '_gat', durationKey: 'minute1' },
],
},
{
key: 'marketing',
icon: Target,
required: false,
cookies: [
{ name: '_fbp', purposeKey: '_fbp', durationKey: 'months3' },
{ name: 'li_fat_id', purposeKey: 'li_fat_id', durationKey: 'days30' },
{ name: 'hubspotutk', purposeKey: 'hubspotutk', durationKey: 'months13' },
],
},
{
key: 'functional',
icon: Settings,
required: false,
cookies: [
{ name: 'language', purposeKey: 'language', durationKey: 'year1' },
{ name: 'theme', purposeKey: 'theme', durationKey: 'year1' },
{ name: 'recent_searches', purposeKey: 'recent_searches', durationKey: 'days30' },
],
},
]; ];
export default function CookiesPage() { export default function CookiesPage() {
@ -230,6 +199,11 @@ export default function CookiesPage() {
)} )}
</div> </div>
{type.items.length === 0 ? (
<p className="rounded-xl border border-dashed border-border px-4 py-6 text-center text-sm text-neutral-500">
{t('noneInUse')}
</p>
) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
@ -237,6 +211,9 @@ export default function CookiesPage() {
<th className="text-left py-3 px-4 font-semibold text-brand-navy"> <th className="text-left py-3 px-4 font-semibold text-brand-navy">
{t('tableHeaders.name')} {t('tableHeaders.name')}
</th> </th>
<th className="text-left py-3 px-4 font-semibold text-brand-navy">
{t('tableHeaders.storage')}
</th>
<th className="text-left py-3 px-4 font-semibold text-brand-navy"> <th className="text-left py-3 px-4 font-semibold text-brand-navy">
{t('tableHeaders.purpose')} {t('tableHeaders.purpose')}
</th> </th>
@ -246,22 +223,26 @@ export default function CookiesPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{type.cookies.map(cookie => ( {type.items.map(item => (
<tr key={cookie.name} className="border-b border-border last:border-0"> <tr key={item.name} className="border-b border-border last:border-0">
<td className="py-3 px-4 font-mono text-brand-turquoise"> <td className="py-3 px-4 font-mono text-brand-turquoise">
{cookie.name} {item.name}
</td>
<td className="py-3 px-4 text-neutral-600">
{t(`purposes.${cookie.purposeKey}` as any)}
</td> </td>
<td className="py-3 px-4 text-neutral-500"> <td className="py-3 px-4 text-neutral-500">
{t(`durations.${cookie.durationKey}` as any)} {t(`storageKinds.${item.kind}`)}
</td>
<td className="py-3 px-4 text-neutral-600">
{t(`storage.${item.name}.purpose`)}
</td>
<td className="py-3 px-4 text-neutral-500">
{t(`storage.${item.name}.duration`)}
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
)}
</motion.div> </motion.div>
); );
})} })}

View File

@ -12,19 +12,51 @@ import {
Globe, Globe,
Mail, Mail,
Calendar, Calendar,
Building2,
Sparkles,
Share2,
Scale,
type LucideIcon, type LucideIcon,
} from 'lucide-react'; } from 'lucide-react';
import { LandingHeader, LandingFooter } from '@/components/layout'; import { LandingHeader, LandingFooter } from '@/components/layout';
import { CONTROLLER, SUBPROCESSORS, SUPERVISORY_AUTHORITY } from '@/lib/legal/processing';
const SECTION_KEYS = ['data', 'use', 'protection', 'rights', 'transfers', 'retention'] as const; /**
* Politique de confidentialite.
*
* La version precedente citait le Privacy Shield comme garantie de transfert —
* un mecanisme invalide par la CJUE en juillet 2020 — ne nommait aucun
* sous-traitant, ne donnait aucune base legale et passait sous silence le
* traitement par un modele de langage. Le texte vit dans les messages ; la
* liste des destinataires vient de `lib/legal/processing`, la meme source que
* la page de conformite de l'administration, pour que les deux ne puissent pas
* diverger.
*/
const ICONS: Record<(typeof SECTION_KEYS)[number], LucideIcon> = { const SECTION_KEYS = [
'controller',
'data',
'purposes',
'ai',
'recipients',
'transfers',
'retention',
'rights',
'security',
] as const;
type SectionKey = (typeof SECTION_KEYS)[number];
const ICONS: Record<SectionKey, LucideIcon> = {
controller: Building2,
data: Database, data: Database,
use: Eye, purposes: Eye,
protection: Lock, ai: Sparkles,
rights: UserCheck, recipients: Share2,
transfers: Globe, transfers: Globe,
retention: Calendar, retention: Calendar,
rights: UserCheck,
security: Lock,
}; };
function renderInlineBold(content: string) { function renderInlineBold(content: string) {
@ -44,30 +76,29 @@ export default function PrivacyPage() {
const containerVariants = { const containerVariants = {
hidden: { opacity: 0, y: 50 }, hidden: { opacity: 0, y: 50 },
visible: { visible: { opacity: 1, y: 0, transition: { duration: 0.6, staggerChildren: 0.1 } },
opacity: 1,
y: 0,
transition: {
duration: 0.6,
staggerChildren: 0.1,
},
},
}; };
const itemVariants = { const itemVariants = {
hidden: { opacity: 0, y: 20 }, hidden: { opacity: 0, y: 20 },
visible: { visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
opacity: 1,
y: 0,
transition: { duration: 0.5 },
},
}; };
// Seuls les champs renseignes sont publies : mieux vaut une mention
// manquante, que la console d'administration signale, qu'une mention fausse.
const identityRows = [
['tradingName', CONTROLLER.tradingName],
['legalName', CONTROLLER.legalName],
['registrationNumber', CONTROLLER.registrationNumber],
['address', CONTROLLER.address],
['contact', CONTROLLER.privacyContact],
['dpo', CONTROLLER.dpo],
].filter(([, value]) => Boolean(value)) as [string, string][];
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-white">
<LandingHeader /> <LandingHeader />
{/* Hero Section */}
<section <section
ref={heroRef} ref={heroRef}
className="relative pt-32 pb-20 bg-gradient-to-br from-brand-navy to-brand-navy/95 overflow-hidden" className="relative pt-32 pb-20 bg-gradient-to-br from-brand-navy to-brand-navy/95 overflow-hidden"
@ -120,9 +151,34 @@ export default function PrivacyPage() {
</div> </div>
</section> </section>
{/* Content Section */}
<section ref={contentRef} className="py-20"> <section ref={contentRef} className="py-20">
<div className="max-w-4xl mx-auto px-6 lg:px-8"> <div className="max-w-4xl mx-auto px-6 lg:px-8">
{/* Identite du responsable : la premiere chose que doit trouver
quelqu'un qui veut savoir a qui s'adresser (art. 13.1.a). */}
{identityRows.length > 0 && (
<motion.dl
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5 }}
className="mb-12 grid gap-x-8 gap-y-4 rounded-2xl border border-border bg-neutral-50 p-8 sm:grid-cols-2"
>
{identityRows.map(([key, value]) => (
<div key={key}>
<dt className="text-sm text-neutral-500">{t(`identity.${key}`)}</dt>
<dd className="mt-1 font-medium text-brand-navy">
{key === 'contact' ? (
<a className="hover:text-brand-turquoise" href={`mailto:${value}`}>
{value}
</a>
) : (
value
)}
</dd>
</div>
))}
</motion.dl>
)}
<motion.div <motion.div
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@ -141,13 +197,14 @@ export default function PrivacyPage() {
<div className="w-12 h-12 bg-brand-turquoise/10 rounded-xl flex items-center justify-center flex-shrink-0"> <div className="w-12 h-12 bg-brand-turquoise/10 rounded-xl flex items-center justify-center flex-shrink-0">
<IconComponent className="w-6 h-6 text-brand-turquoise" /> <IconComponent className="w-6 h-6 text-brand-turquoise" />
</div> </div>
<div> <div className="min-w-0 flex-1">
<h2 className="text-2xl font-bold text-brand-navy mb-4"> <h2 className="text-2xl font-bold text-brand-navy mb-4">
{t(`sections.${key}.title`)} {t(`sections.${key}.title`)}
</h2> </h2>
<div className="text-neutral-600 leading-relaxed whitespace-pre-line prose prose-sm max-w-none"> <div className="text-neutral-600 leading-relaxed whitespace-pre-line prose prose-sm max-w-none">
{renderInlineBold(t(`sections.${key}.content`))} {renderInlineBold(t(`sections.${key}.content`))}
</div> </div>
{key === 'recipients' && <SubprocessorTable />}
</div> </div>
</div> </div>
</motion.div> </motion.div>
@ -155,22 +212,46 @@ export default function PrivacyPage() {
})} })}
</motion.div> </motion.div>
{/* Contact Section */} {/* Droit de reclamation (art. 13.2.d) : absent de la version
precedente, alors qu'il doit etre porte a la connaissance. */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.4 }}
className="mt-12 flex flex-col gap-4 rounded-2xl border border-border bg-neutral-50 p-8 sm:flex-row sm:items-center sm:justify-between"
>
<div>
<h3 className="flex items-center gap-2 text-lg font-bold text-brand-navy">
<Scale className="h-5 w-5 text-brand-turquoise" />
{t('complaint.title')}
</h3>
<p className="mt-2 text-neutral-600">{t('complaint.body')}</p>
</div>
<a
href={SUPERVISORY_AUTHORITY.url}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 rounded-lg border border-brand-navy px-5 py-3 text-center font-medium text-brand-navy transition-colors hover:bg-brand-navy hover:text-white"
>
{t('complaint.cta')}
</a>
</motion.div>
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}} animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.6 }} transition={{ duration: 0.8, delay: 0.6 }}
className="mt-16 bg-gradient-to-br from-brand-navy to-brand-navy/95 p-10 rounded-3xl text-center" className="mt-8 bg-gradient-to-br from-brand-navy to-brand-navy/95 p-10 rounded-3xl text-center"
> >
<Mail className="w-12 h-12 text-brand-turquoise mx-auto mb-4" /> <Mail className="w-12 h-12 text-brand-turquoise mx-auto mb-4" />
<h3 className="text-2xl font-bold text-white mb-4">{t('contact.title')}</h3> <h3 className="text-2xl font-bold text-white mb-4">{t('contact.title')}</h3>
<p className="text-white/80 mb-6">{t('contact.body')}</p> <p className="text-white/80 mb-6">{t('contact.body')}</p>
<a <a
href="mailto:privacy@xpeditis.com" href={`mailto:${CONTROLLER.privacyContact}`}
className="inline-flex items-center space-x-2 px-6 py-3 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-colors font-medium" className="inline-flex items-center space-x-2 px-6 py-3 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-colors font-medium"
> >
<Mail className="w-5 h-5" /> <Mail className="w-5 h-5" />
<span>privacy@xpeditis.com</span> <span>{CONTROLLER.privacyContact}</span>
</a> </a>
</motion.div> </motion.div>
</div> </div>
@ -180,3 +261,55 @@ export default function PrivacyPage() {
</div> </div>
); );
} }
/**
* Registre des sous-traitants.
*
* Un tableau plutot qu'une liste a puces : la question qu'on se pose devant
* cette section est « qui recoit quoi, et ou », et elle se lit en colonnes.
*/
function SubprocessorTable() {
const t = useTranslations('marketing.privacy.recipientsTable');
const tProcessor = useTranslations('marketing.privacy.processors');
return (
<div className="mt-6 -mx-2 overflow-x-auto px-2">
<table className="w-full min-w-[42rem] border-collapse text-left text-sm">
<caption className="sr-only">{t('caption')}</caption>
<thead>
<tr className="border-b border-border">
{(['name', 'role', 'data', 'location', 'basis'] as const).map(column => (
<th key={column} scope="col" className="py-3 pr-4 font-semibold text-brand-navy">
{t(column)}
</th>
))}
</tr>
</thead>
<tbody>
{SUBPROCESSORS.map(processor => (
<tr key={processor.key} className="border-b border-border/60 align-top">
<th scope="row" className="py-4 pr-4 font-medium text-brand-navy">
{processor.name}
</th>
<td className="py-4 pr-4 text-neutral-600">
{tProcessor(`${processor.key}.role`)}
{processor.conditional && (
<span className="mt-1 block text-xs text-neutral-500">
{tProcessor(`${processor.key}.conditional`)}
</span>
)}
</td>
<td className="py-4 pr-4 text-neutral-600">{tProcessor(`${processor.key}.data`)}</td>
<td className="py-4 pr-4 text-neutral-600">
{tProcessor(`${processor.key}.location`)}
</td>
<td className="py-4 pr-4 text-neutral-600">
{t(`bases.${processor.transferBasis}`)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

View File

@ -55,6 +55,76 @@
"notAdmin": "Access denied: this account is not an administrator.", "notAdmin": "Access denied: this account is not an administrator.",
"error": "Sign in failed. Please check your credentials.", "error": "Sign in failed. Please check your credentials.",
"backToApp": "Back to site" "backToApp": "Back to site"
},
"compliance": {
"title": "Compliance",
"description": "What the product declares, what it keeps, and the record of rights requests.",
"refresh": "Refresh",
"errorTitle": "Compliance status could not be loaded",
"errorBody": "The service did not respond. Try again in a moment.",
"disclaimer": "This page describes what the code actually enforces. It does not replace review by legal counsel.",
"identity": {
"incompleteTitle": "Required details missing",
"incompleteBody": "The privacy policy must identify the data controller (Article 13.1.a). These details are not filled in, so they are not published:",
"where": "Fill them in at apps/frontend/src/lib/legal/processing.ts, the CONTROLLER constant.",
"completeTitle": "Controller identity complete",
"completeBody": "{name} is identified on the privacy policy with all required details.",
"fields": {
"legalName": "Registered company name",
"registrationNumber": "Company number",
"address": "Registered office address"
}
},
"retention": {
"title": "Retention periods",
"subtitle": "The rules as the server enforces them, and what has passed its deadline.",
"table": "Table",
"duration": "Period",
"onErasure": "On erasure request",
"expired": "Expired",
"basis": "Justification",
"months": "{count} months",
"accountLifetime": "Account lifetime",
"actions": {
"delete": "Deleted",
"anonymise": "Anonymised",
"keep": "Kept"
},
"autoOn": "Automatic purge active",
"autoOnBody": "Expired data is deleted every night at 3am.",
"autoOff": "Automatic purge disabled",
"autoOffBody": "Nothing is deleted automatically, yet the privacy policy states these periods. Set RETENTION_PURGE_ENABLED=true once you have checked the table below."
},
"purge": {
"action": "Purge now",
"confirmTitle": "Delete expired data?",
"confirmBody": "{count} rows past their retention period will be permanently deleted. This cannot be undone.",
"done": "{count} rows deleted",
"failed": "The purge could not be applied"
},
"processors": {
"title": "Processor register",
"subtitle": "Providers processing data on behalf of Xpeditis (Article 30).",
"name": "Provider",
"role": "Role",
"location": "Processing location",
"basis": "Transfer safeguard"
},
"requests": {
"title": "Rights requests",
"subtitle": "Record of exports and erasures handled (Article 5.2).",
"date": "Date",
"action": "Request",
"subject": "Data subject",
"emptyTitle": "No request recorded",
"emptyBody": "Exports and erasures will appear here as soon as someone exercises their rights.",
"actions": {
"gdpr_data_exported": "Data export",
"gdpr_erasure_executed": "Account erasure",
"gdpr_consent_recorded": "Consent recorded",
"gdpr_retention_purge": "Retention purge"
}
}
} }
}, },
"language": { "language": {
@ -3190,7 +3260,8 @@
"blogDesc": "Create and manage blog articles", "blogDesc": "Create and manage blog articles",
"logs": "System logs", "logs": "System logs",
"logsDesc": "View and download application logs", "logsDesc": "View and download application logs",
"assistant": "AI assistant" "assistant": "AI assistant",
"compliance": "Compliance"
} }
}, },
"csvUpload": { "csvUpload": {
@ -3587,42 +3658,111 @@
"footer": "All prices are in euros excl. tax. Yearly billing = 11 months." "footer": "All prices are in euros excl. tax. Yearly billing = 11 months."
}, },
"common": { "common": {
"lastUpdated": "Last updated: January 2025" "lastUpdated": "Last updated: September 2026"
}, },
"privacy": { "privacy": {
"badge": "Data protection", "badge": "Data protection",
"title1": "Privacy", "title1": "Privacy",
"title2": "Policy", "title2": "Policy",
"intro": "At Xpeditis, protecting your personal data is a top priority. This policy explains how we collect, use, and protect your information.", "intro": "How Xpeditis collects, uses and protects your data — and what you can require of us. Our use of cookies is detailed in the cookie policy.",
"sections": { "identity": {
"data": { "title": "Data controller",
"title": "1. Data we collect", "tradingName": "Trading name",
"content": "We collect the following data:\n\n• **Identification data**: first name, last name, business email address, phone number\n• **Business data**: company name, role, business registration number\n• **Connection data**: IP address, login records, browsing data\n• **Transaction data**: booking history, quotes, invoices\n• **Communication data**: exchanges with our customer service" "legalName": "Registered name",
"registrationNumber": "Company number",
"address": "Registered office",
"contact": "Data protection contact",
"dpo": "Data protection officer"
}, },
"transfers": { "recipientsTable": {
"title": "5. International transfers", "caption": "Processors within the meaning of Article 28 GDPR",
"content": "Your data may be transferred to non-EU countries as part of our international maritime freight services. These transfers are governed by:\n\n• Standard contractual clauses approved by the European Commission\n• Appropriate certifications (e.g. Privacy Shield for some providers)\n• Explicit consent for certain specific transfers" "name": "Provider",
}, "role": "Role",
"retention": { "data": "Data shared",
"title": "6. Data retention", "location": "Processing location",
"content": "We retain your data for the following durations:\n\n• **Account data**: duration of the business relationship + 3 years\n• **Transaction data**: 10 years (accounting obligations)\n• **Connection data**: 1 year\n• **Marketing data**: 3 years after the last contact\n\nAfter these periods, your data is deleted or anonymised." "basis": "Transfer safeguard",
}, "bases": {
"rights": { "eu": "Processed in the European Union",
"title": "4. Your rights", "adequacy": "Adequacy decision",
"content": "Under the GDPR, you have the following rights:\n\n• **Right of access**: get a copy of your personal data\n• **Right of rectification**: correct your inaccurate data\n• **Right of erasure**: request deletion of your data\n• **Right of portability**: receive your data in a structured format\n• **Right to object**: object to the processing of your data\n• **Right to restriction**: limit the processing of your data\n\nTo exercise these rights, contact us at: privacy@xpeditis.com" "scc": "Standard contractual clauses"
},
"use": {
"title": "2. Use of data",
"content": "Your data is used to:\n\n• Provide and improve our maritime freight booking services\n• Manage your account and preferences\n• Process your quote requests and bookings\n• Send you commercial communications (with your consent)\n• Ensure the security of our platform\n• Meet our legal and regulatory obligations"
},
"protection": {
"title": "3. Data protection",
"content": "We implement robust security measures:\n\n• SSL/TLS encryption for all communications\n• Encryption of sensitive data at rest (AES-256)\n• Two-factor authentication available\n• Regular security audits\n• Continuous training of our teams\n• Hosting on ISO 27001-certified servers"
} }
}, },
"sections": {
"controller": {
"title": "1. Who processes your data",
"content": "Xpeditis operates a professional LCL ocean freight booking platform at www.xpeditis.com, and acts as the controller for the data described here.\n\nAny question, access request or exercise of your rights: **privacy@xpeditis.com**. We answer within one month."
},
"data": {
"title": "2. Data we process",
"content": "• **Identity and contact** : name, business email address, phone number\n• **Company** : registered name, SIREN and SIRET numbers, address, EORI number\n• **Account** : role, language, last sign-in, cookie preferences\n• **Commercial activity** : rate searches, booking requests, transport documents, amounts and commissions\n• **Assistant conversations** : the content of the questions you ask and the answers you receive\n• **Security and technical** : IP address, browser used, sign-in and sensitive-action logs\n\nWe process no special category data within the meaning of Article 9 GDPR: no health data, no opinions, no biometrics. The service is aimed at professionals, and what you provide concerns your business activity."
},
"purposes": {
"title": "3. Why, and on what legal basis",
"content": "Each processing activity rests on a specific legal basis (Article 6):\n\n• **Providing the service** — rate search, bookings, documents, invoicing: *performance of the contract*\n• **Managing your account and authentication** — sign-in, two-factor, password reset: *performance of the contract*\n• **Verifying the company at registration** through its SIREN or SIRET: *legitimate interest in contracting only with identified professionals*\n• **Securing the platform** — access logs, intrusion detection, rate limiting: *legitimate interest*\n• **Retaining accounting records**: *legal obligation*\n• **Notifying you about your bookings**: *performance of the contract*\n• **Non-essential cookies and commercial communications**: *your consent*, withdrawable at any time\n\nNo decision producing legal effects concerning you is taken solely by automated means (Article 22). Displayed rates and assistant answers support your decision; the decision itself stays human."
},
"ai": {
"title": "4. Assistant and artificial intelligence",
"content": "The assistant built into the platform runs on OpenAI models.\n\n• The content of your messages is sent to OpenAI to produce the answer.\n• These exchanges are **not** used to train models: processing goes through OpenAI's business offering, which excludes that use contractually.\n• The assistant can read your own data — your bookings, your organisation — within the limits of your access rights, and every such lookup is logged.\n• Your conversations are kept so you can find them again, and deleted on request.\n\nAs with any service relying on an external provider, do not enter information you would not want leaving the platform."
},
"recipients": {
"title": "5. Who receives your data",
"content": "Your data is never sold, rented or shared for advertising purposes.\n\nIt is accessible to members of your organisation according to their rights, to specifically authorised Xpeditis staff, and to the carrier concerned when you send them a booking request — in which case only what the carrier needs to perform the transport is shared.\n\nIt is also processed, on our behalf and on our instructions, by the following processors:"
},
"transfers": {
"title": "6. Transfers outside the European Union",
"content": "Some of the providers above process data in the United States. These transfers are governed by the **standard contractual clauses** adopted by the European Commission on 4 June 2021, supplemented by technical measures: encryption in transit and at rest, and limiting transferred data to what is strictly necessary.\n\nWe rely on no Privacy Shield certification: that mechanism was invalidated by the Court of Justice of the European Union on 16 July 2020 (Schrems II, case C-311/18).\n\nA copy of the applicable safeguards is available on request at privacy@xpeditis.com."
},
"retention": {
"title": "7. Retention periods",
"content": "• **Account and profile** : for the duration of the commercial relationship. On closure, the account is anonymised.\n• **Bookings and accounting records** : 10 years, under Article L123-22 of the French Commercial Code\n• **Security logs** : 12 months\n• **Notifications** : 12 months\n• **Assistant conversations** : 12 months\n• **Proof of cookie consent** : 13 months\n\nAt the end of these periods, data is deleted or anonymised. Anonymised data can no longer identify you: it is no longer personal data."
},
"rights": {
"title": "8. Your rights",
"content": "• **Access and portability** : download all of your data from your account, as JSON or a spreadsheet, without writing to us\n• **Rectification** : correct your details in your settings, or ask us to do it\n• **Erasure** : deleting your account erases your notifications, conversations, API keys and preferences, and anonymises your account and the logs. Accounting records are kept without your identity, as the law requires.\n• **Objection and restriction** : you may object to processing based on our legitimate interest, or ask for it to be restricted\n• **Withdrawing consent** : at any time, as easily as it was given, without affecting what was done beforehand\n\nWe answer within one month (Article 12.3). If our answer does not satisfy you, you may lodge a complaint with the CNIL."
},
"security": {
"title": "9. Security",
"content": "• All communications encrypted with TLS\n• Passwords protected with Argon2: never stored in clear text, never readable by our staff\n• Session tokens set as httpOnly cookies, out of reach of page JavaScript\n• Two-factor authentication available\n• Logging of sensitive actions and administrator access\n• Sign-in attempt limiting\n\nNo measure removes all risk. Should a breach be likely to result in a high risk to your rights, you will be informed (Article 34)."
}
},
"complaint": {
"title": "Complaints",
"body": "Do you believe your data is not being handled lawfully? You can lodge a complaint with the supervisory authority:",
"cta": "Contact the CNIL"
},
"contact": { "contact": {
"title": "Any questions?", "title": "Questions?",
"body": "For any question regarding our privacy policy or your personal data, contact our Data Protection Officer." "body": "For any question about this policy or your personal data, write to us. We answer within one month."
},
"processors": {
"stripe": {
"role": "Card payment processing and subscription management",
"data": "Billing identity, email address, amounts. Card numbers never pass through Xpeditis.",
"location": "Ireland and the United States"
},
"openai": {
"role": "International trade assistant: generating answers and indexing the documentation",
"data": "The content of the messages you send to the assistant, and the documentation excerpts used to answer",
"location": "United States",
"conditional": "Only if you use the assistant."
},
"sentry": {
"role": "Detecting technical errors in the application",
"data": "Error message, page concerned, technical account identifier, IP address",
"location": "United States"
},
"pappers": {
"role": "Verifying SIREN and SIRET company numbers at registration",
"data": "The company identification number you enter",
"location": "France",
"conditional": "Only when verifying a company."
},
"oauth": {
"role": "Signing in with a Google or Microsoft account",
"data": "Email address, first and last name of the account used to sign in",
"location": "European Union and United States",
"conditional": "Only if you choose that sign-in method."
}
} }
}, },
"terms": { "terms": {
@ -3672,7 +3812,7 @@
"intro": "Discover how we use cookies to improve your experience on Xpeditis and how you can manage your preferences.", "intro": "Discover how we use cookies to improve your experience on Xpeditis and how you can manage your preferences.",
"introBoxTitle": "What is a cookie?", "introBoxTitle": "What is a cookie?",
"introBoxBody1": "A cookie is a small text file stored on your device (computer, tablet, smartphone) when you visit a website. Cookies allow the site to remember your actions and preferences over a period of time.", "introBoxBody1": "A cookie is a small text file stored on your device (computer, tablet, smartphone) when you visit a website. Cookies allow the site to remember your actions and preferences over a period of time.",
"introBoxBody2": "Cookies do not contain personally identifiable information and cannot access data stored on your device.", "introBoxBody2": "A cookie can be personal data: the session cookies below link you to an account. That is why they are only set to make the service work, or with your agreement.",
"typesTitle": "Types of cookies used", "typesTitle": "Types of cookies used",
"typesSubtitle": "We use different types of cookies on our platform", "typesSubtitle": "We use different types of cookies on our platform",
"required": "Required", "required": "Required",
@ -3680,7 +3820,8 @@
"tableHeaders": { "tableHeaders": {
"name": "Name", "name": "Name",
"purpose": "Purpose", "purpose": "Purpose",
"duration": "Duration" "duration": "Duration",
"storage": "Stored as"
}, },
"types": { "types": {
"essential": { "essential": {
@ -3710,29 +3851,32 @@
"title": "Questions about cookies?", "title": "Questions about cookies?",
"body": "Our team is available to answer all your questions regarding the use of cookies on our platform." "body": "Our team is available to answer all your questions regarding the use of cookies on our platform."
}, },
"purposes": { "storage": {
"session_id": "Maintains your login session", "accessToken": {
"csrf_token": "Protects against CSRF attacks", "purpose": "Keeps you signed in. Out of reach of page JavaScript.",
"cookie_consent": "Stores your cookie preferences", "duration": "15 minutes"
"_ga": "Google Analytics — visitor identification",
"_gid": "Google Analytics — session identification",
"_gat": "Google Analytics — request rate limiting",
"_fbp": "Facebook Pixel — conversion tracking",
"li_fat_id": "LinkedIn Insight — marketing attribution",
"hubspotutk": "HubSpot — visitor identification",
"language": "Stores your preferred language",
"theme": "Stores the theme (light/dark)",
"recent_searches": "History of your recent searches"
}, },
"durations": { "refreshToken": {
"session": "Session", "purpose": "Renews the session without asking for your password again",
"year1": "1 year", "duration": "7 days"
"years2": "2 years", },
"hours24": "24 hours", "xpeditis_session": {
"minute1": "1 minute", "purpose": "Tells the interface a session exists. Contains no token.",
"months3": "3 months", "duration": "15 minutes"
"days30": "30 days", },
"months13": "13 months" "cookieConsent": {
"purpose": "Remembers your choices, so you are not asked again on every visit",
"duration": "13 months"
},
"NEXT_LOCALE": {
"purpose": "Remembers the language you chose",
"duration": "1 year"
}
},
"noneInUse": "No tracker in this category is in use today. Should that change, this page will be updated and your consent asked for beforehand.",
"storageKinds": {
"cookie": "Cookie",
"localStorage": "Local storage"
} }
}, },
"about": { "about": {
@ -4050,7 +4194,8 @@
"rightsCta": { "rightsCta": {
"text": "To exercise your rights, log into your account or contact our DPO.", "text": "To exercise your rights, log into your account or contact our DPO.",
"login": "Access my account", "login": "Access my account",
"dpo": "Contact the DPO" "dpo": "Contact the DPO",
"contact": "Contact the data protection team"
}, },
"principlesTitle": "Our data protection principles", "principlesTitle": "Our data protection principles",
"principlesSubtitle": "Fundamental principles that guide our data processing", "principlesSubtitle": "Fundamental principles that guide our data processing",
@ -4105,6 +4250,10 @@
"title": "Contact our DPO", "title": "Contact our DPO",
"body": "Our Data Protection Officer is available for any questions related to the processing of your personal data or the exercise of your rights.", "body": "Our Data Protection Officer is available for any questions related to the processing of your personal data or the exercise of your rights.",
"privacyLink": "Privacy policy" "privacyLink": "Privacy policy"
},
"contact": {
"title": "Contact us",
"body": "For any question about how your personal data is handled, or to exercise your rights, write to us. We answer within one month."
} }
}, },
"blog": { "blog": {

View File

@ -55,6 +55,76 @@
"notAdmin": "Accès refusé : ce compte n'est pas administrateur.", "notAdmin": "Accès refusé : ce compte n'est pas administrateur.",
"error": "Échec de la connexion. Vérifiez vos identifiants.", "error": "Échec de la connexion. Vérifiez vos identifiants.",
"backToApp": "Retour au site" "backToApp": "Retour au site"
},
"compliance": {
"title": "Conformité",
"description": "Ce que le produit déclare, ce qu'il conserve, et la trace des demandes de droits.",
"refresh": "Actualiser",
"errorTitle": "Impossible de charger l'état de conformité",
"errorBody": "Le service n'a pas répondu. Réessayez dans un instant.",
"disclaimer": "Cette page décrit ce que le code applique réellement. Elle ne remplace pas la validation par un conseil juridique.",
"identity": {
"incompleteTitle": "Mentions obligatoires manquantes",
"incompleteBody": "La politique de confidentialité doit identifier le responsable du traitement (article 13.1.a). Ces informations ne sont pas renseignées, et ne sont donc pas publiées :",
"where": "À renseigner dans apps/frontend/src/lib/legal/processing.ts, constante CONTROLLER.",
"completeTitle": "Identité du responsable complète",
"completeBody": "{name} est identifié sur la politique de confidentialité avec l'ensemble des mentions requises.",
"fields": {
"legalName": "Raison sociale exacte",
"registrationNumber": "Numéro SIREN",
"address": "Adresse du siège social"
}
},
"retention": {
"title": "Durées de conservation",
"subtitle": "Les règles telles que le serveur les applique, et ce qui a dépassé son échéance.",
"table": "Table",
"duration": "Durée",
"onErasure": "Sur demande d'effacement",
"expired": "Échues",
"basis": "Justification",
"months": "{count} mois",
"accountLifetime": "Vie du compte",
"actions": {
"delete": "Supprimée",
"anonymise": "Anonymisée",
"keep": "Conservée"
},
"autoOn": "Purge automatique active",
"autoOnBody": "Les données échues sont supprimées chaque nuit à 3 h.",
"autoOff": "Purge automatique désactivée",
"autoOffBody": "Rien n'est supprimé automatiquement. La politique de confidentialité annonce pourtant ces durées : activez RETENTION_PURGE_ENABLED=true une fois le tableau ci-dessous vérifié."
},
"purge": {
"action": "Purger maintenant",
"confirmTitle": "Supprimer les données échues ?",
"confirmBody": "{count} lignes ayant dépassé leur durée de conservation seront définitivement supprimées. Cette action est irréversible.",
"done": "{count} lignes supprimées",
"failed": "La purge n'a pas pu être appliquée"
},
"processors": {
"title": "Registre des sous-traitants",
"subtitle": "Les prestataires qui traitent des données pour le compte d'Xpeditis (article 30).",
"name": "Prestataire",
"role": "Rôle",
"location": "Lieu de traitement",
"basis": "Encadrement du transfert"
},
"requests": {
"title": "Demandes de droits",
"subtitle": "Trace des exports et des effacements traités (article 5.2).",
"date": "Date",
"action": "Demande",
"subject": "Personne concernée",
"emptyTitle": "Aucune demande enregistrée",
"emptyBody": "Les exports et effacements apparaîtront ici dès qu'une personne exercera ses droits.",
"actions": {
"gdpr_data_exported": "Export des données",
"gdpr_erasure_executed": "Effacement du compte",
"gdpr_consent_recorded": "Consentement enregistré",
"gdpr_retention_purge": "Purge de conservation"
}
}
} }
}, },
"language": { "language": {
@ -3190,7 +3260,8 @@
"blogDesc": "Créer et gérer les articles du blog", "blogDesc": "Créer et gérer les articles du blog",
"logs": "Logs système", "logs": "Logs système",
"logsDesc": "Visualiser et télécharger les logs applicatifs", "logsDesc": "Visualiser et télécharger les logs applicatifs",
"assistant": "Assistant IA" "assistant": "Assistant IA",
"compliance": "Conformité"
} }
}, },
"csvUpload": { "csvUpload": {
@ -3587,42 +3658,111 @@
"footer": "Tous les prix sont en euros HT. Facturation annuelle = 11 mois." "footer": "Tous les prix sont en euros HT. Facturation annuelle = 11 mois."
}, },
"common": { "common": {
"lastUpdated": "Dernière mise à jour : Janvier 2025" "lastUpdated": "Dernière mise à jour : septembre 2026"
}, },
"privacy": { "privacy": {
"badge": "Protection des données", "badge": "Protection des données",
"title1": "Politique de", "title1": "Politique de",
"title2": "Confidentialité", "title2": "Confidentialité",
"intro": "Chez Xpeditis, la protection de vos données personnelles est une priorité absolue. Cette politique explique comment nous collectons, utilisons et protégeons vos informations.", "intro": "Comment Xpeditis collecte, utilise et protège vos données — et ce que vous pouvez exiger de nous. Nos usages de cookies sont détaillés dans la politique de cookies.",
"identity": {
"title": "Responsable du traitement",
"tradingName": "Nom commercial",
"legalName": "Raison sociale",
"registrationNumber": "SIREN",
"address": "Siège social",
"contact": "Contact protection des données",
"dpo": "Délégué à la protection des données"
},
"recipientsTable": {
"caption": "Sous-traitants au sens de l'article 28 du RGPD",
"name": "Prestataire",
"role": "Rôle",
"data": "Données transmises",
"location": "Lieu de traitement",
"basis": "Encadrement du transfert",
"bases": {
"eu": "Traitement dans l'Union européenne",
"adequacy": "Décision d'adéquation",
"scc": "Clauses contractuelles types"
}
},
"sections": { "sections": {
"controller": {
"title": "1. Qui traite vos données",
"content": "Xpeditis exploite une plateforme professionnelle de réservation de fret maritime en groupage, accessible sur www.xpeditis.com, et agit en qualité de responsable du traitement pour les données décrites ici.\n\nToute question, demande d'accès ou exercice de vos droits : **privacy@xpeditis.com**. Nous répondons sous un mois."
},
"data": { "data": {
"title": "1. Données collectées", "title": "2. Données que nous traitons",
"content": "Nous collectons les données suivantes :\n\n• **Données d'identification** : nom, prénom, adresse email professionnelle, numéro de téléphone\n• **Données professionnelles** : nom de l'entreprise, fonction, numéro SIRET\n• **Données de connexion** : adresse IP, logs de connexion, données de navigation\n• **Données de transaction** : historique des réservations, devis, factures\n• **Données de communication** : échanges avec notre service client" "content": "• **Identité et contact** : nom, prénom, adresse email professionnelle, numéro de téléphone\n• **Entreprise** : raison sociale, SIREN et SIRET, adresse, numéro EORI\n• **Compte** : rôle, langue, date de dernière connexion, préférences de cookies\n• **Activité commerciale** : recherches de tarifs, demandes de réservation, documents de transport, montants et commissions\n• **Échanges avec l'assistant** : le contenu des questions posées et des réponses reçues\n• **Sécurité et technique** : adresse IP, navigateur utilisé, journal des connexions et des actions sensibles\n\nNous ne traitons aucune donnée sensible au sens de l'article 9 du RGPD : ni santé, ni opinions, ni données biométriques. Le service s'adresse à des professionnels, et les informations que vous fournissez concernent votre activité."
},
"purposes": {
"title": "3. Pourquoi, et sur quelle base légale",
"content": "Chaque traitement repose sur une base légale précise (article 6) :\n\n• **Fournir le service** — recherche de tarifs, réservations, documents, facturation : *exécution du contrat*\n• **Gérer votre compte et l'authentification** — connexion, double facteur, réinitialisation du mot de passe : *exécution du contrat*\n• **Vérifier l'entreprise à l'inscription** via son SIREN ou SIRET : *intérêt légitime à ne contracter qu'avec des professionnels identifiés*\n• **Sécuriser la plateforme** — journal des accès, détection des tentatives d'intrusion, limitation du débit : *intérêt légitime*\n• **Conserver les pièces comptables** : *obligation légale*\n• **Vous notifier de l'avancement de vos réservations** : *exécution du contrat*\n• **Cookies non essentiels et communications commerciales** : *votre consentement*, retirable à tout moment\n\nAucune décision produisant des effets juridiques à votre égard n'est prise de manière entièrement automatisée (article 22). Les tarifs affichés et les réponses de l'assistant sont des aides à la décision : la validation reste humaine."
},
"ai": {
"title": "4. Assistant et intelligence artificielle",
"content": "L'assistant intégré à la plateforme s'appuie sur les modèles d'OpenAI.\n\n• Le contenu de vos messages est transmis à OpenAI pour produire la réponse.\n• Ces échanges ne servent **pas** à entraîner de modèle : le traitement passe par l'offre professionnelle d'OpenAI, qui exclut cet usage par contrat.\n• L'assistant peut consulter vos propres données — vos réservations, votre organisation — dans la limite de vos droits d'accès, et chacune de ces consultations est journalisée.\n• Vos conversations sont conservées pour que vous puissiez les retrouver, et effacées sur simple demande.\n\nComme pour tout service reposant sur un prestataire externe, n'y saisissez pas d'information que vous ne souhaitez pas voir quitter la plateforme."
},
"recipients": {
"title": "5. Destinataires de vos données",
"content": "Vos données ne sont ni vendues, ni louées, ni cédées à des fins publicitaires.\n\nY accèdent : les membres de votre organisation selon leurs droits, les équipes d'Xpeditis spécifiquement habilitées, et le transporteur concerné lorsque vous lui adressez une demande de réservation — dans ce cas, seuls les éléments nécessaires à l'exécution du transport lui sont transmis.\n\nElles sont par ailleurs traitées, pour notre compte et sur nos instructions, par les sous-traitants suivants :"
}, },
"transfers": { "transfers": {
"title": "5. Transferts internationaux", "title": "6. Transferts hors Union européenne",
"content": "Vos données peuvent être transférées vers des pays hors UE dans le cadre de nos services de fret maritime international. Ces transferts sont encadrés par :\n\n• Des clauses contractuelles types approuvées par la Commission européenne\n• Des certifications adéquates (ex: Privacy Shield pour certains prestataires)\n• Le consentement explicite pour certains transferts spécifiques" "content": "Certains des prestataires ci-dessus traitent des données aux États-Unis. Ces transferts sont encadrés par les **clauses contractuelles types** adoptées par la Commission européenne le 4 juin 2021, complétées par des mesures techniques : chiffrement en transit et au repos, et limitation des données transmises au strict nécessaire.\n\nNous ne nous prévalons d'aucune certification Privacy Shield : ce mécanisme a été invalidé par la Cour de justice de l'Union européenne le 16 juillet 2020 (arrêt Schrems II, affaire C-311/18).\n\nUne copie des garanties applicables peut vous être communiquée sur demande à privacy@xpeditis.com."
}, },
"retention": { "retention": {
"title": "6. Conservation des données", "title": "7. Durées de conservation",
"content": "Nous conservons vos données selon les durées suivantes :\n\n• **Données de compte** : durée de la relation commerciale + 3 ans\n• **Données de transaction** : 10 ans (obligations comptables)\n• **Données de connexion** : 1 an\n• **Données marketing** : 3 ans après le dernier contact\n\nÀ l'expiration de ces délais, vos données sont supprimées ou anonymisées." "content": "• **Compte et profil** : durée de la relation commerciale. À sa fermeture, le compte est anonymisé.\n• **Réservations et pièces comptables** : 10 ans, conformément à l'article L123-22 du code de commerce\n• **Journaux de sécurité** : 12 mois\n• **Notifications** : 12 mois\n• **Conversations avec l'assistant** : 12 mois\n• **Preuve du consentement aux cookies** : 13 mois\n\nAu terme de ces durées, les données sont supprimées ou anonymisées. Une donnée anonymisée ne permet plus de vous identifier : elle n'est plus une donnée personnelle."
}, },
"rights": { "rights": {
"title": "4. Vos droits", "title": "8. Vos droits",
"content": "Conformément au RGPD, vous disposez des droits suivants :\n\n• **Droit d'accès** : obtenir une copie de vos données personnelles\n• **Droit de rectification** : corriger vos données inexactes\n• **Droit à l'effacement** : demander la suppression de vos données\n• **Droit à la portabilité** : recevoir vos données dans un format structuré\n• **Droit d'opposition** : vous opposer au traitement de vos données\n• **Droit de limitation** : limiter le traitement de vos données\n\nPour exercer ces droits, contactez-nous à : privacy@xpeditis.com" "content": "• **Accès et portabilité** : téléchargez l'intégralité de vos données depuis votre espace, en JSON ou en tableur, sans avoir à nous écrire\n• **Rectification** : corrigez vos informations depuis vos paramètres, ou demandez-nous de le faire\n• **Effacement** : la suppression de votre compte efface vos notifications, vos conversations, vos clés d'API et vos préférences, et anonymise votre compte ainsi que les journaux. Les pièces comptables sont conservées sans votre identité, comme la loi l'impose.\n• **Opposition et limitation** : vous pouvez vous opposer à un traitement fondé sur notre intérêt légitime, ou en demander la limitation\n• **Retrait du consentement** : à tout moment, aussi simplement qu'il a été donné, sans effet sur ce qui a été fait auparavant\n\nNous répondons sous un mois (article 12.3). Si notre réponse ne vous satisfait pas, vous pouvez saisir la CNIL."
}, },
"use": { "security": {
"title": "2. Utilisation des données", "title": "9. Sécurité",
"content": "Vos données sont utilisées pour :\n\n• Fournir et améliorer nos services de réservation de fret maritime\n• Gérer votre compte et vos préférences\n• Traiter vos demandes de devis et réservations\n• Vous envoyer des communications commerciales (avec votre consentement)\n• Assurer la sécurité de notre plateforme\n• Respecter nos obligations légales et réglementaires" "content": "• Chiffrement de toutes les communications par TLS\n• Mots de passe protégés par Argon2 : ni stockés en clair, ni lisibles par nos équipes\n• Jetons de session déposés en cookies httpOnly, inaccessibles au JavaScript de la page\n• Double authentification disponible\n• Journalisation des actions sensibles et des accès administrateurs\n• Limitation du nombre de tentatives de connexion\n\nAucune mesure n'élimine tout risque. En cas de violation susceptible d'engendrer un risque élevé pour vos droits, vous en serez informé (article 34)."
},
"protection": {
"title": "3. Protection des données",
"content": "Nous mettons en œuvre des mesures de sécurité robustes :\n\n• Chiffrement SSL/TLS pour toutes les communications\n• Chiffrement des données sensibles au repos (AES-256)\n• Authentification à deux facteurs disponible\n• Audits de sécurité réguliers\n• Formation continue de nos équipes\n• Hébergement sur des serveurs certifiés ISO 27001"
} }
}, },
"complaint": {
"title": "Réclamation",
"body": "Vous estimez que le traitement de vos données ne respecte pas la réglementation ? Vous pouvez introduire une réclamation auprès de l'autorité de contrôle :",
"cta": "Saisir la CNIL"
},
"contact": { "contact": {
"title": "Des questions ?", "title": "Des questions ?",
"body": "Pour toute question concernant notre politique de confidentialité ou vos données personnelles, contactez notre Délégué à la Protection des Données." "body": "Pour toute question sur cette politique ou sur vos données personnelles, écrivez-nous. Nous répondons sous un mois."
},
"processors": {
"stripe": {
"role": "Encaissement des paiements par carte et gestion des abonnements",
"data": "Identité de facturation, adresse email, montants. Les numéros de carte ne transitent jamais par Xpeditis.",
"location": "Irlande et États-Unis"
},
"openai": {
"role": "Assistant commerce international : génération des réponses et indexation de la documentation",
"data": "Le contenu des messages envoyés à l'assistant et les extraits de documentation utilisés pour répondre",
"location": "États-Unis",
"conditional": "Uniquement si vous utilisez l'assistant."
},
"sentry": {
"role": "Détection des erreurs techniques de l'application",
"data": "Message d'erreur, page concernée, identifiant technique du compte, adresse IP",
"location": "États-Unis"
},
"pappers": {
"role": "Vérification des numéros SIREN et SIRET à l'inscription",
"data": "Le numéro d'identification de l'entreprise saisi",
"location": "France",
"conditional": "Uniquement lors de la vérification d'une entreprise."
},
"oauth": {
"role": "Connexion par compte Google ou Microsoft",
"data": "Adresse email, nom et prénom du compte utilisé pour se connecter",
"location": "Union européenne et États-Unis",
"conditional": "Uniquement si vous choisissez ce mode de connexion."
}
} }
}, },
"terms": { "terms": {
@ -3672,7 +3812,7 @@
"intro": "Découvrez comment nous utilisons les cookies pour améliorer votre expérience sur Xpeditis et comment vous pouvez gérer vos préférences.", "intro": "Découvrez comment nous utilisons les cookies pour améliorer votre expérience sur Xpeditis et comment vous pouvez gérer vos préférences.",
"introBoxTitle": "Qu'est-ce qu'un cookie ?", "introBoxTitle": "Qu'est-ce qu'un cookie ?",
"introBoxBody1": "Un cookie est un petit fichier texte stocké sur votre appareil (ordinateur, tablette, smartphone) lorsque vous visitez un site web. Les cookies permettent au site de mémoriser vos actions et préférences sur une période donnée.", "introBoxBody1": "Un cookie est un petit fichier texte stocké sur votre appareil (ordinateur, tablette, smartphone) lorsque vous visitez un site web. Les cookies permettent au site de mémoriser vos actions et préférences sur une période donnée.",
"introBoxBody2": "Les cookies ne contiennent pas d'informations personnellement identifiables et ne peuvent pas accéder aux données stockées sur votre appareil.", "introBoxBody2": "Un cookie peut constituer une donnée personnelle : les cookies de session ci-dessous permettent de vous rattacher à un compte. C'est pourquoi ils ne sont déposés que pour faire fonctionner le service, ou avec votre accord.",
"typesTitle": "Types de cookies utilisés", "typesTitle": "Types de cookies utilisés",
"typesSubtitle": "Nous utilisons différents types de cookies sur notre plateforme", "typesSubtitle": "Nous utilisons différents types de cookies sur notre plateforme",
"required": "Requis", "required": "Requis",
@ -3680,7 +3820,8 @@
"tableHeaders": { "tableHeaders": {
"name": "Nom", "name": "Nom",
"purpose": "Finalité", "purpose": "Finalité",
"duration": "Durée" "duration": "Durée",
"storage": "Emplacement"
}, },
"types": { "types": {
"essential": { "essential": {
@ -3710,29 +3851,32 @@
"title": "Des questions sur les cookies ?", "title": "Des questions sur les cookies ?",
"body": "Notre équipe est disponible pour répondre à toutes vos questions concernant l'utilisation des cookies sur notre plateforme." "body": "Notre équipe est disponible pour répondre à toutes vos questions concernant l'utilisation des cookies sur notre plateforme."
}, },
"purposes": { "storage": {
"session_id": "Maintien de votre session de connexion", "accessToken": {
"csrf_token": "Protection contre les attaques CSRF", "purpose": "Maintien de votre session. Inaccessible au JavaScript de la page.",
"cookie_consent": "Mémorisation de vos préférences cookies", "duration": "15 minutes"
"_ga": "Google Analytics - Identification des visiteurs",
"_gid": "Google Analytics - Identification des sessions",
"_gat": "Google Analytics - Limitation du taux de requêtes",
"_fbp": "Facebook Pixel - Suivi des conversions",
"li_fat_id": "LinkedIn Insight - Attribution marketing",
"hubspotutk": "HubSpot - Identification des visiteurs",
"language": "Mémorisation de votre langue préférée",
"theme": "Mémorisation du thème (clair/sombre)",
"recent_searches": "Historique de vos recherches récentes"
}, },
"durations": { "refreshToken": {
"session": "Session", "purpose": "Renouvellement de la session sans nouvelle saisie du mot de passe",
"year1": "1 an", "duration": "7 jours"
"years2": "2 ans", },
"hours24": "24 heures", "xpeditis_session": {
"minute1": "1 minute", "purpose": "Indique à l'interface qu'une session existe. Ne contient aucun jeton.",
"months3": "3 mois", "duration": "15 minutes"
"days30": "30 jours", },
"months13": "13 mois" "cookieConsent": {
"purpose": "Mémorisation de vos choix, pour ne pas vous les redemander à chaque visite",
"duration": "13 mois"
},
"NEXT_LOCALE": {
"purpose": "Mémorisation de la langue choisie",
"duration": "1 an"
}
},
"noneInUse": "Aucun traceur de cette catégorie n'est utilisé aujourd'hui. Si cela change, cette page sera mise à jour et votre consentement sera demandé au préalable.",
"storageKinds": {
"cookie": "Cookie",
"localStorage": "Stockage local"
} }
}, },
"about": { "about": {
@ -4050,7 +4194,8 @@
"rightsCta": { "rightsCta": {
"text": "Pour exercer vos droits, connectez-vous à votre compte ou contactez notre DPO.", "text": "Pour exercer vos droits, connectez-vous à votre compte ou contactez notre DPO.",
"login": "Accéder à mon compte", "login": "Accéder à mon compte",
"dpo": "Contacter le DPO" "dpo": "Contacter le DPO",
"contact": "Contacter le service protection des données"
}, },
"principlesTitle": "Nos principes de protection des données", "principlesTitle": "Nos principes de protection des données",
"principlesSubtitle": "Des principes fondamentaux qui guident notre traitement des données", "principlesSubtitle": "Des principes fondamentaux qui guident notre traitement des données",
@ -4105,6 +4250,10 @@
"title": "Contacter notre DPO", "title": "Contacter notre DPO",
"body": "Notre Délégué à la Protection des Données est à votre disposition pour toute question relative au traitement de vos données personnelles ou à l'exercice de vos droits.", "body": "Notre Délégué à la Protection des Données est à votre disposition pour toute question relative au traitement de vos données personnelles ou à l'exercice de vos droits.",
"privacyLink": "Politique de confidentialité" "privacyLink": "Politique de confidentialité"
},
"contact": {
"title": "Nous contacter",
"body": "Pour toute question sur le traitement de vos données personnelles ou pour exercer vos droits, écrivez-nous. Nous répondons sous un mois."
} }
}, },
"blog": { "blog": {