mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Compare commits
2
Commits
09b2acf4fe
...
82ae5e289d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82ae5e289d | ||
|
|
642b6020c3 |
No files matched your search
@@ -585,6 +585,9 @@ function App() {
|
||||
const [familyTabs, setFamilyTabsState] = useState(() => { try { return localStorage.getItem('parenthese.familyTabs') !== 'off' } catch { return true } })
|
||||
const setFamilyTabs = useCallback((on) => { setFamilyTabsState(on); try { localStorage.setItem('parenthese.familyTabs', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
|
||||
// Nom de famille sous le prénom dans l'arbre, celui de naissance s'il est connu (#168). Réglage de ce navigateur.
|
||||
// Proches des autres familles dessinés, estompés, autour d'un onglet : 0 (ma ligne seulement) à 3 liens
|
||||
const [tabKinDepth, setTabKinDepthState] = useState(() => { try { const v = localStorage.getItem('parenthese.tabKinDepth'); return ['0', '1', '2', '3'].includes(v) ? Number(v) : 1 } catch { return 1 } })
|
||||
const setTabKinDepth = useCallback((d) => { setTabKinDepthState(d); try { localStorage.setItem('parenthese.tabKinDepth', String(d)) } catch { /* navigateur sans stockage */ } }, [])
|
||||
const [fullNames, setFullNamesState] = useState(() => { try { return localStorage.getItem('parenthese.fullNames') === 'on' } catch { return false } })
|
||||
const setFullNames = useCallback((on) => { setFullNamesState(on); try { localStorage.setItem('parenthese.fullNames', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
|
||||
// Premiers visages : fenêtre ouverte après un import GEDCOM, ou depuis les paramètres de l'arbre
|
||||
@@ -3571,6 +3574,7 @@ function App() {
|
||||
reorderEnabled={canReorderSiblings}
|
||||
onReorderSiblings={handleReorderSiblings}
|
||||
familyTabs={familyTabs}
|
||||
tabKinDepth={tabKinDepth}
|
||||
fullNames={fullNames}
|
||||
annotationViewRef={annotationViewRef}
|
||||
onAnnotationsAnchored={persistAnnotationAnchors}
|
||||
@@ -3752,6 +3756,8 @@ function App() {
|
||||
onClose={() => setAdminPanelVisible(false)}
|
||||
familyTabs={familyTabs}
|
||||
onFamilyTabsChange={setFamilyTabs}
|
||||
tabKinDepth={tabKinDepth}
|
||||
onTabKinDepthChange={setTabKinDepth}
|
||||
fullNames={fullNames}
|
||||
onFullNamesChange={setFullNames}
|
||||
/>
|
||||
|
||||
@@ -41,6 +41,8 @@ const TREE_ICONS = ['🌳', '🌿', '🌻', '🏡', '❤️', '🧡', '⭐', '
|
||||
const AdminPanel = ({
|
||||
familyTabs = true,
|
||||
onFamilyTabsChange = null,
|
||||
tabKinDepth = 1,
|
||||
onTabKinDepthChange = null,
|
||||
fullNames = false,
|
||||
onFullNamesChange = null,
|
||||
visible,
|
||||
@@ -535,6 +537,7 @@ const AdminPanel = ({
|
||||
|
||||
<fieldset className="ts-policy">
|
||||
<legend className="pz-eyebrow">{t('Affichage')}</legend>
|
||||
<p className="pz-small">{t('Réglages de ce navigateur.')}</p>
|
||||
<label className={`ts-choice ${familyTabs ? 'is-active' : ''}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -543,10 +546,21 @@ const AdminPanel = ({
|
||||
onChange={(event) => onFamilyTabsChange?.(event.target.checked)}
|
||||
/>
|
||||
<span className="ts-choice-text">
|
||||
<strong>{t('Les grandes familles par alliance dans des onglets')}</strong>
|
||||
<small>{t("Une belle-famille plus large qu'un écran s'ouvre dans son propre onglet au lieu d'être dessinée dans l'arbre. Réglage de ce navigateur.")}</small>
|
||||
<strong>{t('Séparer les belles-familles en onglets')}</strong>
|
||||
<small>{t("Quand la famille d'un conjoint est trop large pour l'écran, elle s'ouvre dans un onglet en haut de l'arbre.")}</small>
|
||||
</span>
|
||||
</label>
|
||||
<div className="pz-field">
|
||||
<span className="pz-label" id="settingsTabKinLabel">{t("Dans un onglet, montrer aussi les proches de l'autre côté")}</span>
|
||||
<div className="ts-icons" role="radiogroup" aria-labelledby="settingsTabKinLabel">
|
||||
{[[0, t('Ma ligne seulement')], [1, t('1 lien')], [2, t('2 liens')], [3, t('3 liens')]].map(([d, text]) => (
|
||||
<button key={d} type="button" role="radio" aria-checked={tabKinDepth === d} disabled={!familyTabs} className="ts-icon ts-icon--letter" onClick={() => onTabKinDepthChange?.(d)}>
|
||||
{text}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="pz-hint">{t('Un lien : un parent, un enfant ou un conjoint. Ils apparaissent en plus clair ; votre propre ligne reste toujours visible.')}</p>
|
||||
</div>
|
||||
<label className={`ts-choice ${fullNames ? 'is-active' : ''}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -555,8 +569,8 @@ const AdminPanel = ({
|
||||
onChange={(event) => onFullNamesChange?.(event.target.checked)}
|
||||
/>
|
||||
<span className="ts-choice-text">
|
||||
<strong>{t("Le nom de famille sous le prénom")}</strong>
|
||||
<small>{t("Le nom de naissance quand il est connu, sinon le nom d'usage. Réglage de ce navigateur.")}</small>
|
||||
<strong>{t('Afficher le nom de famille')}</strong>
|
||||
<small>{t("Sous chaque prénom. Le nom de naissance s'il est connu.")}</small>
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getPersonMedias, getParents, getChildren, getPersonUnions, persons as g
|
||||
import { PERSON_R, FLOAT_SPEED, FLOAT_AMPLITUDE, REPULSION_RADIUS, REPULSION_STRENGTH, MAX_ORBIT_MEDIAS, ENTRANCE_NODE_DURATION, ENTRANCE_CAMERA_DURATION, ENTRANCE_CAMERA_FROM_SCALE, ENTRANCE_REDUCED_DURATION, ENTRANCE_LINE_DELAY, ENTRANCE_LINE_DURATION, ENTRANCE_ORBIT_DELAY, ENTRANCE_ORBIT_DURATION, ENTRANCE_ORBIT_STAGGER } from './galaxy/constants'
|
||||
import { easeOutCubic } from './galaxy/utils'
|
||||
import { computeLayout, generateNodeRandomData, coupleBarMeta, restoreCoupleBarMeta } from './galaxy/elkLayout'
|
||||
import { computeBlockLayout, familyTabsOf, lineToFamily, subgraphOf, takeRootLayout } from './galaxy/blockLayout'
|
||||
import { computeBlockLayout, familyTabsOf, kinAround, lineToFamily, subgraphOf, takeRootLayout } from './galaxy/blockLayout'
|
||||
import { hasGhosts, withGhosts } from '../utils/ghosts'
|
||||
|
||||
// Placement par blocs familiaux par défaut (mesures : tools/layout-bench). ?layout=sugiyama = placement Sugiyama.
|
||||
@@ -77,7 +77,7 @@ function barTabs(tabs, activeKey) {
|
||||
})
|
||||
}
|
||||
|
||||
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, fullNames = false, annotationViewRef = null, onAnnotationsAnchored = null, selfPersonId = null, ghosts = null, initialFamilyKey = null, onFamilyChange = null }) => {
|
||||
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, tabKinDepth = 1, fullNames = false, annotationViewRef = null, onAnnotationsAnchored = null, selfPersonId = null, ghosts = null, initialFamilyKey = null, onFamilyChange = null }) => {
|
||||
const canvasRef = useRef(null)
|
||||
const layoutDataRef = useRef(null)
|
||||
// Onglets de familles (réglage `familyTabs`) : la liste des familles de l'arbre, calculée une fois par
|
||||
@@ -675,6 +675,12 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
// Dans l'onglet d'une autre famille, la personne commune (« C'est moi », sinon la personne de départ) reste
|
||||
// là, reliée par sa ligne directe (Patch, 21/10 : « je veux me voir tout le temps »)
|
||||
const line = tab?.personIds ? lineToFamily(selfPersonId ?? startRefRef.current ?? tabsCacheRef.current.tabs[0]?.resolvedRef, tab.personIds) : new Set()
|
||||
// Autour, estompés aussi : l'autre parent des porteurs du nom venus d'ailleurs, et les proches de l'autre côté
|
||||
// jusqu'à la profondeur choisie dans les réglages (Patch, 06/11)
|
||||
if (tab?.personIds) {
|
||||
for (const id of tab.faded || []) line.add(id)
|
||||
for (const id of kinAround(tab.personIds, tabKinDepth)) line.add(id)
|
||||
}
|
||||
const tabGraph = tab?.personIds ? subgraphOf(new Set([...tab.personIds, ...line])) : null
|
||||
const graph = hasGhosts(ghosts)
|
||||
? withGhosts(tabGraph || { persons: graphPersons, unions: graphUnions, filiations: graphFiliations }, ghosts, !tabGraph)
|
||||
@@ -875,7 +881,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
pendingFrameRef.current = null
|
||||
requestAnimationFrame(() => zoomApiRef?.current?.focusPersons?.(ids))
|
||||
}
|
||||
}, [graphRevision, activeKey, familyTabs, selfPersonId, ghosts, requestTabs, cancelTabsRequest])
|
||||
}, [graphRevision, activeKey, familyTabs, tabKinDepth, selfPersonId, ghosts, requestTabs, cancelTabsRequest])
|
||||
|
||||
// Onglet ouvert, remonté pour l'adresse de la page et le lien de partage ; null pour la famille de départ
|
||||
useEffect(() => {
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
.ts-icon--letter { width: auto; padding: 0 12px; border-radius: 999px; font: 500 13px var(--pz-sans); color: var(--pz-ink-2); }
|
||||
.ts-icon[aria-checked="true"] { border-color: var(--pz-a500); background: var(--pz-a50); box-shadow: 0 0 0 3px var(--pz-a100); }
|
||||
.ts-icon:focus-visible { outline: 2px solid var(--pz-a500); outline-offset: 2px; }
|
||||
.ts-icon:disabled { opacity: 0.5; cursor: default; }
|
||||
|
||||
/* Cinq onglets : ils rétrécissent (texte sur deux lignes) au lieu de déborder du panneau sur un petit téléphone */
|
||||
.ts-tabs .pz-tab { min-width: 0; }
|
||||
|
||||
@@ -854,34 +854,35 @@ export function subgraphOf(personIds, graphData = null) {
|
||||
return { persons, unions, filiations }
|
||||
}
|
||||
|
||||
// La ligne directe d'une personne vers une famille (Patch, 21/10 : « je veux me voir tout le temps ») : le plus
|
||||
// court chemin de parenté (parents, enfants, conjoints) de `selfId` jusqu'au premier membre de `memberIds`,
|
||||
// sans fratries autour. Chaque pas d'un enfant à un parent garde ses deux parents (le fil part de leur union,
|
||||
// et sans l'autre parent le placement dessine un conjoint « Inconnu »).
|
||||
// Rend les personnes à ajouter (ni `selfId` déjà membre, ni chemin : ensemble vide).
|
||||
export function lineToFamily(selfId, memberIds, graphData = null) {
|
||||
const members = new Set([...memberIds].map(String))
|
||||
const self = selfId != null ? String(selfId) : null
|
||||
if (self == null || members.has(self)) return new Set()
|
||||
const persons = graphData?.persons || globalPersons
|
||||
if (!persons.some(p => String(p.id) === self)) return new Set()
|
||||
// La parenté d'un arbre : pour chaque personne, ses voisins (conjoints, parents, enfants), chacun avec les personnes
|
||||
// que ce pas fait dessiner en plus. Chaque pas entre un enfant et un parent garde l'autre parent (le fil part de
|
||||
// leur union, et sans l'autre parent le placement dessine un conjoint « Inconnu »).
|
||||
function kinshipOf(graphData = null) {
|
||||
const unions = graphData?.unions || globalUnions
|
||||
const filiations = graphData?.filiations || globalFiliations
|
||||
const unionById = new Map(unions.map(u => [String(u.id), u]))
|
||||
// Voisins : [voisin, personnes que ce pas fait dessiner en plus]
|
||||
const next = new Map()
|
||||
const link = (a, b, extra = []) => { if (!next.has(a)) next.set(a, []); next.get(a).push([b, extra]) }
|
||||
const partnersOf = new Map()
|
||||
for (const u of unions) {
|
||||
const a = String(u.partner1Id), b = String(u.partner2Id)
|
||||
link(a, b); link(b, a)
|
||||
if (!partnersOf.has(a)) partnersOf.set(a, new Set())
|
||||
if (!partnersOf.has(b)) partnersOf.set(b, new Set())
|
||||
partnersOf.get(a).add(b); partnersOf.get(b).add(a)
|
||||
}
|
||||
const parentsOf = new Map()
|
||||
const childrenOf = new Map()
|
||||
for (const f of filiations) {
|
||||
const child = String(f.childId)
|
||||
const u = f.unionId ? unionById.get(String(f.unionId)) : null
|
||||
const parents = u ? [String(u.partner1Id), String(u.partner2Id)] : f.parentId != null ? [String(f.parentId)] : []
|
||||
if (!parentsOf.has(child)) parentsOf.set(child, new Set())
|
||||
for (const p of parents) parentsOf.get(child).add(p)
|
||||
for (const p of parents) {
|
||||
parentsOf.get(child).add(p)
|
||||
if (!childrenOf.has(p)) childrenOf.set(p, new Set())
|
||||
childrenOf.get(p).add(child)
|
||||
}
|
||||
}
|
||||
for (const [child, parents] of parentsOf) {
|
||||
for (const p of parents) {
|
||||
@@ -889,6 +890,20 @@ export function lineToFamily(selfId, memberIds, graphData = null) {
|
||||
link(child, p, couple); link(p, child, couple)
|
||||
}
|
||||
}
|
||||
return { next, parentsOf, childrenOf, partnersOf }
|
||||
}
|
||||
|
||||
// La ligne directe d'une personne vers une famille (Patch, 21/10 : « je veux me voir tout le temps ») : le plus
|
||||
// court chemin de parenté (parents, enfants, conjoints) de `selfId` jusqu'au premier membre de `memberIds`,
|
||||
// sans fratries autour, chaque pas d'un enfant à un parent gardant ses deux parents.
|
||||
// Rend les personnes à ajouter (ni `selfId` déjà membre, ni chemin : ensemble vide).
|
||||
export function lineToFamily(selfId, memberIds, graphData = null) {
|
||||
const members = new Set([...memberIds].map(String))
|
||||
const self = selfId != null ? String(selfId) : null
|
||||
if (self == null || members.has(self)) return new Set()
|
||||
const persons = graphData?.persons || globalPersons
|
||||
if (!persons.some(p => String(p.id) === self)) return new Set()
|
||||
const { next } = kinshipOf(graphData)
|
||||
const from = new Map([[self, null]])
|
||||
const queue = [self]
|
||||
let hit = null
|
||||
@@ -909,6 +924,60 @@ export function lineToFamily(selfId, memberIds, graphData = null) {
|
||||
return line
|
||||
}
|
||||
|
||||
// Les porteurs du nom d'une famille (Patch, 06/11 : « si un gonz a un nom de famille il fait partie de la famille
|
||||
// au même nom ») : un enfant d'un membre qui porte le nom de l'onglet en devient membre, puis ses enfants au même
|
||||
// nom, de génération en génération. Nom de naissance s'il est connu, sinon nom d'usage (une femme mariée notée au
|
||||
// nom de son mari reste chez elle). Seuls les enfants d'un membre comptent : un homonyme sans lien reste dehors.
|
||||
// Le couple d'un porteur est dessiné entier (`added` prend ses conjoints) ; l'autre parent d'un porteur ajouté,
|
||||
// sans le nom, est rendu à part (`coParents`) pour être estompé, comme un proche de l'autre côté.
|
||||
const nomCle = (s) => (s || '').trim().toLocaleLowerCase('fr')
|
||||
export function bearersOfName(memberIds, name, graphData = null) {
|
||||
const added = new Set()
|
||||
const coParents = new Set()
|
||||
const target = nomCle(name)
|
||||
if (!target) return { added, coParents }
|
||||
const persons = graphData?.persons || globalPersons
|
||||
const nameOf = new Map(persons.map(p => [String(p.id), nomCle(p.birthName || p.lastName)]))
|
||||
const { parentsOf, childrenOf, partnersOf } = kinshipOf(graphData)
|
||||
const members = new Set([...memberIds].map(String))
|
||||
const bearers = []
|
||||
const queue = [...members]
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
for (const c of childrenOf.get(queue[i]) || []) {
|
||||
if (members.has(c) || added.has(c) || nameOf.get(c) !== target) continue
|
||||
added.add(c); bearers.push(c); queue.push(c)
|
||||
}
|
||||
}
|
||||
const inTab = (id) => members.has(id) || added.has(id)
|
||||
for (const b of bearers) for (const p of partnersOf.get(b) || []) if (!inTab(p)) added.add(p)
|
||||
for (const b of bearers) for (const p of parentsOf.get(b) || []) if (!inTab(p)) coParents.add(p)
|
||||
return { added, coParents }
|
||||
}
|
||||
|
||||
// Les proches de l'autre côté d'un onglet (réglage « Proches des autres familles ») : les personnes à `depth` pas
|
||||
// de parenté au plus d'un membre, chaque pas d'un enfant à un parent gardant l'autre parent. Estompées à l'écran.
|
||||
export function kinAround(memberIds, depth, graphData = null) {
|
||||
const around = new Set()
|
||||
if (!(depth > 0)) return around
|
||||
const members = new Set([...memberIds].map(String))
|
||||
const { next } = kinshipOf(graphData)
|
||||
const dist = new Map([...members].map(id => [id, 0]))
|
||||
const queue = [...members]
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
const d = dist.get(queue[i])
|
||||
if (d >= depth) continue
|
||||
for (const [nb, extra] of next.get(queue[i]) || []) {
|
||||
for (const e of extra) if (!members.has(e)) around.add(e)
|
||||
if (dist.has(nb)) continue
|
||||
dist.set(nb, d + 1)
|
||||
around.add(nb)
|
||||
queue.push(nb)
|
||||
}
|
||||
}
|
||||
for (const id of members) around.delete(id)
|
||||
return around
|
||||
}
|
||||
|
||||
// Toutes les familles d'un arbre, de proche en proche : l'arbre de la personne de départ, puis chaque grande
|
||||
// belle-famille qu'il cache (un onglet = ce bloc caché plus le conjoint charnière, posé avec lui comme
|
||||
// référence), puis celles que ces onglets cachent à leur tour. Chaque personne est dans un seul onglet, sauf
|
||||
@@ -933,7 +1002,9 @@ export function familyTabsOf(graphData = null, referenceId = null) {
|
||||
const key = String(f.anchorId).replace(/^p-/, '')
|
||||
if (seen.has(key)) continue
|
||||
seen.add(key)
|
||||
tabs.push({ key, referenceId: key, parentKey: tab.key, personIds: new Set([...f.members, ...f.hinges]), hinges: f.hinges, label: f.label, count: f.count })
|
||||
const base = new Set([...f.members, ...f.hinges])
|
||||
const { added, coParents } = bearersOfName(base, f.label, graphData)
|
||||
tabs.push({ key, referenceId: key, parentKey: tab.key, personIds: new Set([...base, ...added]), faded: coParents, hinges: f.hinges, label: f.label, count: f.count + added.size })
|
||||
}
|
||||
}
|
||||
// Deux onglets au même nom (homonymes, ou une branche sortie de sa propre lignée) : sauf le plus grand, chacun
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { bearersOfName, kinAround } from './blockLayout'
|
||||
|
||||
// Henri Garnier (membre de l'onglet Garnier) et Odette, née Morel, notée au nom de son mari.
|
||||
// Leurs enfants : Paul Garnier (marié à Julie Petit, une fille Lou Garnier) et Anne, au nom de sa mère.
|
||||
// Un Garnier sans lien, ailleurs dans l'arbre.
|
||||
const p = (id, firstName, lastName, birthName = null) => ({ id, firstName, lastName, birthName })
|
||||
const graph = {
|
||||
persons: [
|
||||
p(1, 'Henri', 'Garnier'), p(2, 'Odette', 'Garnier', 'Morel'), p(3, 'Paul', 'Garnier'), p(4, 'Anne', 'Morel'),
|
||||
p(5, 'Julie', 'Petit'), p(6, 'Lou', 'Garnier'), p(7, 'Jean', 'Garnier'), p(8, 'Marc', 'Morel'),
|
||||
],
|
||||
unions: [{ id: 'u1', partner1Id: 1, partner2Id: 2 }, { id: 'u2', partner1Id: 3, partner2Id: 5 }],
|
||||
filiations: [
|
||||
{ childId: 3, unionId: 'u1' }, { childId: 4, unionId: 'u1' }, { childId: 6, unionId: 'u2' }, { childId: 2, parentId: 8 },
|
||||
],
|
||||
}
|
||||
|
||||
describe('bearersOfName', () => {
|
||||
const { added, coParents } = bearersOfName(['1'], 'Garnier', graph)
|
||||
it('prend les enfants au nom, de génération en génération, avec leurs conjoints', () => {
|
||||
expect([...added].sort()).toEqual(['3', '5', '6'])
|
||||
})
|
||||
it("laisse dehors l'enfant au nom de sa mère, l'homonyme sans lien, et la mère notée au nom du mari", () => {
|
||||
expect(added.has('4')).toBe(false)
|
||||
expect(added.has('7')).toBe(false)
|
||||
expect(added.has('2')).toBe(false)
|
||||
})
|
||||
it("rend l'autre parent à estomper", () => {
|
||||
expect([...coParents]).toEqual(['2'])
|
||||
})
|
||||
it('ignore la casse du nom', () => {
|
||||
expect(bearersOfName(['1'], 'GARNIER', graph).added.has('3')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('kinAround', () => {
|
||||
it('ma ligne seulement : personne autour', () => {
|
||||
expect(kinAround(['1'], 0, graph).size).toBe(0)
|
||||
})
|
||||
it('1 lien : conjoint et enfants, et l\'autre parent', () => {
|
||||
expect([...kinAround(['1'], 1, graph)].sort()).toEqual(['2', '3', '4'])
|
||||
})
|
||||
it('2 liens : petits-enfants, conjoints des enfants, parents du conjoint', () => {
|
||||
expect([...kinAround(['1'], 2, graph)].sort()).toEqual(['2', '3', '4', '5', '6', '8'])
|
||||
})
|
||||
})
|
||||
@@ -5,10 +5,17 @@ export default {
|
||||
'Famille': 'Family',
|
||||
'Familles': 'Families',
|
||||
'Affichage': 'Display',
|
||||
'Les grandes familles par alliance dans des onglets': 'Large in-law families in tabs',
|
||||
"Une belle-famille plus large qu'un écran s'ouvre dans son propre onglet au lieu d'être dessinée dans l'arbre. Réglage de ce navigateur.": 'An in-law family wider than a screen opens in its own tab instead of being drawn in the tree. Saved in this browser.',
|
||||
'Le nom de famille sous le prénom': 'Last name under the first name',
|
||||
"Le nom de naissance quand il est connu, sinon le nom d'usage. Réglage de ce navigateur.": 'The maiden name when it is known, otherwise the current last name. Saved in this browser.',
|
||||
'Réglages de ce navigateur.': 'Saved in this browser.',
|
||||
'Séparer les belles-familles en onglets': 'Put in-law families in tabs',
|
||||
"Quand la famille d'un conjoint est trop large pour l'écran, elle s'ouvre dans un onglet en haut de l'arbre.": "When a spouse's family is too wide for the screen, it opens in a tab at the top of the tree.",
|
||||
"Dans un onglet, montrer aussi les proches de l'autre côté": 'In a tab, also show relatives from the other side',
|
||||
'Ma ligne seulement': 'Only my line',
|
||||
'1 lien': '1 link',
|
||||
'2 liens': '2 links',
|
||||
'3 liens': '3 links',
|
||||
'Un lien : un parent, un enfant ou un conjoint. Ils apparaissent en plus clair ; votre propre ligne reste toujours visible.': 'A link is a parent, a child or a spouse. They appear lighter; your own line always stays visible.',
|
||||
'Afficher le nom de famille': 'Show the last name',
|
||||
"Sous chaque prénom. Le nom de naissance s'il est connu.": 'Under each first name. The maiden name when it is known.',
|
||||
// Arbre sur canvas (components/galaxy/renderers.js)
|
||||
'Voir les parents': 'See parents',
|
||||
'Voir {firstName}': 'See {firstName}',
|
||||
|
||||
Reference in new issue
Block a user