denckring-fr-data 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- denckring_fr_data/__init__.py +368 -0
- denckring_fr_data/data/glosses.txt.gz +0 -0
- denckring_fr_data/data/graded_words.txt.gz +0 -0
- denckring_fr_data/data/h_aspire.txt.gz +0 -0
- denckring_fr_data/data/metadata.json +12 -0
- denckring_fr_data/data/nouns.txt.gz +0 -0
- denckring_fr_data/data/syllables.txt.gz +0 -0
- denckring_fr_data/data/words.txt.gz +0 -0
- denckring_fr_data/elision.py +329 -0
- denckring_fr_data/py.typed +0 -0
- denckring_fr_data/sampa.py +83 -0
- denckring_fr_data-0.1.0.dist-info/METADATA +57 -0
- denckring_fr_data-0.1.0.dist-info/RECORD +19 -0
- denckring_fr_data-0.1.0.dist-info/WHEEL +4 -0
- denckring_fr_data-0.1.0.dist-info/entry_points.txt +2 -0
- denckring_fr_data-0.1.0.dist-info/licenses/LICENSE +202 -0
- denckring_fr_data-0.1.0.dist-info/licenses/LICENSE-LEXIQUE +20 -0
- denckring_fr_data-0.1.0.dist-info/licenses/LICENSE-WIKTIONARY +17 -0
- denckring_fr_data-0.1.0.dist-info/licenses/NOTICE +15 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""French lexicon data for denckring.
|
|
2
|
+
|
|
3
|
+
Installing this gives the French pack `lexicon.words`, `lexicon.nouns`,
|
|
4
|
+
`lexicon.glosses` and `lexicon.graded_words`. Nothing branches on whether it is
|
|
5
|
+
present — the same procedures answer the same calls, in a third language.
|
|
6
|
+
|
|
7
|
+
Two sources, both CC BY-SA 4.0: Lexique 3.82 for membership, nouns and frequency,
|
|
8
|
+
and French Wiktionary for definitions. ADR 0032 records why Wikidata Lexemes,
|
|
9
|
+
which supplies German, could not supply French.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import gzip
|
|
15
|
+
import re
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
17
|
+
from functools import lru_cache
|
|
18
|
+
from importlib.resources import files
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from types import MappingProxyType
|
|
21
|
+
from typing import ClassVar, TypeVar
|
|
22
|
+
|
|
23
|
+
from denckring.core.errors import MissingCapability
|
|
24
|
+
from denckring.lang.base import (
|
|
25
|
+
ALPHABET,
|
|
26
|
+
FOLD_DIACRITICS,
|
|
27
|
+
GLOSSES,
|
|
28
|
+
GRADED_WORDS,
|
|
29
|
+
LETTER_SHAPES,
|
|
30
|
+
NOUNS,
|
|
31
|
+
PHONEMES,
|
|
32
|
+
SYLLABLES,
|
|
33
|
+
SYLLABLES_DICTIONARY,
|
|
34
|
+
SYLLABLES_HEURISTIC,
|
|
35
|
+
TOKENS,
|
|
36
|
+
WORDS,
|
|
37
|
+
)
|
|
38
|
+
from denckring.lang.fr import FrenchPack
|
|
39
|
+
from denckring_fr_data.elision import count_line
|
|
40
|
+
from denckring_fr_data.sampa import IPA_VOWELS, to_phonemes
|
|
41
|
+
|
|
42
|
+
#: Orthographic vowel runs, for the estimate a word outside Lexique gets.
|
|
43
|
+
#: Answers "roughly how long is this unknown word" from spelling alone --
|
|
44
|
+
#: deliberately not shared with `elision.py`'s pattern (Task 6), which answers
|
|
45
|
+
#: "does this known word end in a mute e" over a different alphabet of
|
|
46
|
+
#: concerns entirely.
|
|
47
|
+
_VOWEL_RUN = re.compile(r"[aeiouyàâäéèêëîïôöùûüÿœæ]+")
|
|
48
|
+
|
|
49
|
+
#: What `look_up` returns is whatever the table holds, and the caller must get
|
|
50
|
+
#: its own type back. The same shape as `denckring_de_wiktionary.look_up`.
|
|
51
|
+
_V = TypeVar("_V", bound=Sequence[str])
|
|
52
|
+
|
|
53
|
+
WORDS_PATH = Path(str(files("denckring_fr_data") / "data" / "words.txt.gz"))
|
|
54
|
+
NOUNS_PATH = Path(str(files("denckring_fr_data") / "data" / "nouns.txt.gz"))
|
|
55
|
+
GRADED_WORDS_PATH = Path(str(files("denckring_fr_data") / "data" / "graded_words.txt.gz"))
|
|
56
|
+
GLOSSES_PATH = Path(str(files("denckring_fr_data") / "data" / "glosses.txt.gz"))
|
|
57
|
+
SYLLABLES_PATH = Path(str(files("denckring_fr_data") / "data" / "syllables.txt.gz"))
|
|
58
|
+
H_ASPIRE_PATH = Path(str(files("denckring_fr_data") / "data" / "h_aspire.txt.gz"))
|
|
59
|
+
|
|
60
|
+
__version__ = "0.1.0"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read(path: Path) -> tuple[str, ...]:
|
|
64
|
+
"""Every line of a gzipped list.
|
|
65
|
+
|
|
66
|
+
A truncated file raises rather than returning a short list: a short noun list
|
|
67
|
+
makes N+7 quietly wrong, and a wrong answer is worse than an exception.
|
|
68
|
+
"""
|
|
69
|
+
with gzip.open(path, "rt", encoding="utf-8") as handle:
|
|
70
|
+
return tuple(line for line in handle.read().split("\n") if line)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@lru_cache(maxsize=1)
|
|
74
|
+
def noun_list() -> tuple[str, ...]:
|
|
75
|
+
"""Every noun form, in dictionary order. N+7 walks this, so order is the point."""
|
|
76
|
+
return _read(NOUNS_PATH)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@lru_cache(maxsize=1)
|
|
80
|
+
def noun_positions() -> dict[str, int]:
|
|
81
|
+
return {word.casefold(): index for index, word in enumerate(noun_list())}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@lru_cache(maxsize=1)
|
|
85
|
+
def known_words() -> frozenset[str]:
|
|
86
|
+
"""Membership, from every form Lexique carries."""
|
|
87
|
+
return frozenset(_read(WORDS_PATH))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@lru_cache(maxsize=1)
|
|
91
|
+
def graded_words() -> Mapping[str, int]:
|
|
92
|
+
"""Word to band, where **larger means less common** — SCOWL's direction."""
|
|
93
|
+
table: dict[str, int] = {}
|
|
94
|
+
with gzip.open(GRADED_WORDS_PATH, mode="rt", encoding="utf-8") as handle:
|
|
95
|
+
for line in handle:
|
|
96
|
+
word, _, band = line.rstrip("\n").partition("\t")
|
|
97
|
+
if band:
|
|
98
|
+
table[word] = int(band)
|
|
99
|
+
return table
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@lru_cache(maxsize=1)
|
|
103
|
+
def gloss_table() -> dict[str, tuple[str, ...]]:
|
|
104
|
+
"""Headword to every sense's definition, in Wiktionary's order.
|
|
105
|
+
|
|
106
|
+
Split on `" | "` rather than `"|"`, because a definition may contain a bare
|
|
107
|
+
pipe and the build rejects any that contains the spaced separator.
|
|
108
|
+
"""
|
|
109
|
+
with gzip.open(GLOSSES_PATH, mode="rt", encoding="utf-8") as handle:
|
|
110
|
+
return {
|
|
111
|
+
key: tuple(values.split(" | "))
|
|
112
|
+
for key, _, values in (line.rstrip("\n").partition("\t") for line in handle)
|
|
113
|
+
if values
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@lru_cache(maxsize=1)
|
|
118
|
+
def syllable_table() -> Mapping[str, tuple[int, str, str]]:
|
|
119
|
+
"""spelling -> (citation syllables, Lexique SAMPA, orthographic syllabation).
|
|
120
|
+
|
|
121
|
+
The third element is the segmented spelling (`car-ros-se`), which serves two
|
|
122
|
+
callers: `syllables()` returns its segments, and the mute-e rules compare
|
|
123
|
+
its segment count against `nbsyll` to judge a final `-ent`.
|
|
124
|
+
"""
|
|
125
|
+
table: dict[str, tuple[int, str, str]] = {}
|
|
126
|
+
for line in _read(SYLLABLES_PATH):
|
|
127
|
+
ortho, nbsyll, phon, osyll = line.split("\t")
|
|
128
|
+
table[ortho] = (int(nbsyll), phon, osyll)
|
|
129
|
+
return MappingProxyType(table)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _table_entry(word: str) -> tuple[int, str, str] | None:
|
|
133
|
+
"""The table entry for `word`, falling back past a leading elision.
|
|
134
|
+
|
|
135
|
+
Task 6's tokeniser (`elision._TOKEN_RE`) deliberately keeps an
|
|
136
|
+
apostrophe-bearing token whole -- `aujourd'hui` and `prud'homme` are 94
|
|
137
|
+
real Lexique entries, and splitting every apostrophe unconditionally
|
|
138
|
+
would silently misread those as a proclitic plus a word. So the whole
|
|
139
|
+
lowercased form is tried first, exactly as before.
|
|
140
|
+
|
|
141
|
+
But not every apostrophe-bearing form is one of those 94: `d'espoir`,
|
|
142
|
+
`l'amour`, `qu'un` are ordinary elision, the proclitic written onto the
|
|
143
|
+
following word, and were never table entries themselves. A whole-form-only
|
|
144
|
+
lookup made `phonemes`/`rhyme_key`/`rhyme_keys`/`syllable_count` fail on
|
|
145
|
+
them with `MissingCapability`, which is a routine event elsewhere but
|
|
146
|
+
silently starved `rhyme_scheme` and `ghazal` of exactly the line-ending
|
|
147
|
+
words French verse ends on most often (fix round 2). The rhyme- and
|
|
148
|
+
syllable-bearing unit of an elided form is what follows the apostrophe,
|
|
149
|
+
so a whole-form miss retries there -- the segment after the LAST
|
|
150
|
+
apostrophe, in case of a chained elision -- before giving up.
|
|
151
|
+
"""
|
|
152
|
+
table = syllable_table()
|
|
153
|
+
lower = word.casefold()
|
|
154
|
+
entry = table.get(lower)
|
|
155
|
+
if entry is not None:
|
|
156
|
+
return entry
|
|
157
|
+
if "'" not in lower and "’" not in lower: # noqa: RUF001
|
|
158
|
+
return None
|
|
159
|
+
tail = re.split(r"['’]", lower)[-1] # noqa: RUF001
|
|
160
|
+
return table.get(tail)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@lru_cache(maxsize=1)
|
|
164
|
+
def h_aspire() -> frozenset[str]:
|
|
165
|
+
"""Words whose initial h blocks elision. Lexique cannot answer this."""
|
|
166
|
+
return frozenset(_read(H_ASPIRE_PATH))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def look_up(table: Mapping[str, _V], word: str) -> _V | None:
|
|
170
|
+
"""The token as written, then case-flipped.
|
|
171
|
+
|
|
172
|
+
French Wiktionary titles are case-sensitive, and the build stores them as
|
|
173
|
+
written: `France`, `Paris` and `Toulouse` are the titles, not `france`,
|
|
174
|
+
`paris`, `toulouse`. Looking a headword up through `_lemma` — which
|
|
175
|
+
casefolds and drops every non-letter — therefore misses every capitalised
|
|
176
|
+
and every hyphenated title. Measured over the shipped table: 261,638 of
|
|
177
|
+
510,973 headwords (51.2%) were unreachable that way, 121,388 of them to case
|
|
178
|
+
alone, and proper nouns are the material loss. Trying the flipped case
|
|
179
|
+
recovers them, and the reverse direction recovers a proper noun someone
|
|
180
|
+
lowercased.
|
|
181
|
+
|
|
182
|
+
On the word **as written**, never `_lemma`'d: accents are meaning here, and
|
|
183
|
+
folding them would collide `côte` with `cote`. Same reason ADR 0009 makes
|
|
184
|
+
folding a decision of the procedure rather than of the lexicon. This is the
|
|
185
|
+
shape `denckring_de_wiktionary.look_up` already uses; French diverged from it
|
|
186
|
+
only by oversight, which is the same key/lookup mismatch fixed for the noun
|
|
187
|
+
list in fdad0c8.
|
|
188
|
+
"""
|
|
189
|
+
for candidate in (word, word.capitalize(), word.lower(), word.upper()):
|
|
190
|
+
found = table.get(candidate)
|
|
191
|
+
if found:
|
|
192
|
+
return found
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class FrenchDataPack(FrenchPack):
|
|
197
|
+
"""French with a lexicon behind it: membership, nouns, glosses and frequency.
|
|
198
|
+
|
|
199
|
+
A fixed `ClassVar`, as on every pack but German's: German's `pack()` factory
|
|
200
|
+
(`denckring_de_data`) exists because two distributions carry German data
|
|
201
|
+
under two different licences and the registry allows only one `de` entry
|
|
202
|
+
point to win. This distribution is the only source of French lexicon data,
|
|
203
|
+
under one licence, so there is nothing for the install to choose between —
|
|
204
|
+
a plain class is the honest shape.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
capabilities: ClassVar[frozenset[str]] = frozenset(
|
|
208
|
+
{
|
|
209
|
+
TOKENS,
|
|
210
|
+
ALPHABET,
|
|
211
|
+
FOLD_DIACRITICS,
|
|
212
|
+
LETTER_SHAPES,
|
|
213
|
+
NOUNS,
|
|
214
|
+
WORDS,
|
|
215
|
+
GLOSSES,
|
|
216
|
+
GRADED_WORDS,
|
|
217
|
+
SYLLABLES,
|
|
218
|
+
SYLLABLES_DICTIONARY,
|
|
219
|
+
SYLLABLES_HEURISTIC,
|
|
220
|
+
PHONEMES,
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def is_word(self, word: str) -> bool:
|
|
225
|
+
return self._lemma(word) in known_words()
|
|
226
|
+
|
|
227
|
+
def syllable_count(self, word: str) -> tuple[int, bool]:
|
|
228
|
+
"""Lexique's citation count, or a vowel-run estimate outside it.
|
|
229
|
+
|
|
230
|
+
The count is the *citation* one: `femme` is one syllable here and
|
|
231
|
+
frequently two in verse. The line rules, not this method, are where
|
|
232
|
+
verse is answered -- spec D3.
|
|
233
|
+
|
|
234
|
+
`_table_entry` tries the whole form first and only then falls back
|
|
235
|
+
past a leading elision (`d'espoir` -> `espoir`), so `aujourd'hui`
|
|
236
|
+
still resolves whole and an ordinary elided form no longer falls
|
|
237
|
+
through to the vowel-run estimate for no reason (fix round 2).
|
|
238
|
+
"""
|
|
239
|
+
entry = _table_entry(word)
|
|
240
|
+
if entry is not None:
|
|
241
|
+
return entry[0], True
|
|
242
|
+
return max(len(_VOWEL_RUN.findall(word.casefold())), 1), False
|
|
243
|
+
|
|
244
|
+
def line_syllables(self, line: str) -> tuple[int, int]:
|
|
245
|
+
"""French counts a line, not a bag of words. Spec D3, ADR 0034."""
|
|
246
|
+
return count_line(line, syllable_table(), h_aspire())
|
|
247
|
+
|
|
248
|
+
def syllables(self, word: str) -> list[str]:
|
|
249
|
+
"""The orthographic segments, or `MissingCapability` for a word
|
|
250
|
+
Lexique does not carry.
|
|
251
|
+
|
|
252
|
+
Not `KeyError`: a plain `KeyError` here would repeat exactly the
|
|
253
|
+
crash fix-round 1 found in `phonemes` and `rhyme_key`/`rhyme_keys`,
|
|
254
|
+
and `MissingCapability` on an out-of-vocabulary word is a routine
|
|
255
|
+
event in real verse rather than an edge case, the same convention
|
|
256
|
+
every other lookup on this pack follows.
|
|
257
|
+
|
|
258
|
+
Task 6 shipped in ADR 0034 and its line rules do not call this
|
|
259
|
+
method -- `line_syllables` reads `syllable_table()` directly (see
|
|
260
|
+
`elision.count_line`), because it needs `nbsyll` and `phon` together
|
|
261
|
+
and `syllables()` exposes neither. As of the whole-branch review
|
|
262
|
+
(2026-09-01) nothing outside the test suite calls `syllables()` at
|
|
263
|
+
all; it exists to answer the `syllables` capability directly, for a
|
|
264
|
+
caller that wants the segmentation itself rather than a count.
|
|
265
|
+
|
|
266
|
+
It diverges from every other lookup on this pack in one respect:
|
|
267
|
+
it is the only one of the four (`syllables`, `phonemes`,
|
|
268
|
+
`syllable_count`, `rhyme_key`) not routed through `_table_entry`, so
|
|
269
|
+
it does not fall back past a leading elision. `phonemes("d'espoir")`
|
|
270
|
+
succeeds (`_table_entry` retries on `espoir` after the whole form
|
|
271
|
+
misses) while `syllables("d'espoir")` raises `MissingCapability` --
|
|
272
|
+
looking the whole elided form up directly, with no retry. This
|
|
273
|
+
divergence is deliberate, not an oversight: `_table_entry`'s retry
|
|
274
|
+
returns the *following* word's entry, and `syllables()`'s job is to
|
|
275
|
+
segment the word exactly as it was asked for, so returning
|
|
276
|
+
`espoir`'s segments (`es-poir`) for the token `d'espoir` would be
|
|
277
|
+
answering a question that was not asked -- silently dropping the
|
|
278
|
+
`d'` rather than reporting that the whole elided form is not one of
|
|
279
|
+
Lexique's 125,653 orthographic keys. No test exercises this path,
|
|
280
|
+
because nothing calls it outside the test suite.
|
|
281
|
+
"""
|
|
282
|
+
entry = syllable_table().get(word.casefold())
|
|
283
|
+
if entry is None:
|
|
284
|
+
raise MissingCapability(f"<word {word!r}>", self.lang, SYLLABLES)
|
|
285
|
+
return entry[2].split("-")
|
|
286
|
+
|
|
287
|
+
def phonemes(self, word: str) -> list[str]:
|
|
288
|
+
"""The IPA transcription, or `MissingCapability` for a word Lexique
|
|
289
|
+
does not carry.
|
|
290
|
+
|
|
291
|
+
Not `KeyError`: `core/prosody.py`'s `word_rhyme_keys` and
|
|
292
|
+
`assonance_constraint`/`spoonerism`'s own lookups catch only
|
|
293
|
+
`MissingCapability` around a pack call, reading it as "this word is
|
|
294
|
+
undecidable" rather than letting it propagate. A plain `KeyError`
|
|
295
|
+
went uncaught there -- `check('assonance_constraint', ..., lang='fr')`
|
|
296
|
+
crashed outright on the first unknown word, which is routine in real
|
|
297
|
+
French text, not an edge case. Matches the convention
|
|
298
|
+
`denckring-en-data` and `denckring-de-wiktionary` already established
|
|
299
|
+
for exactly this call.
|
|
300
|
+
|
|
301
|
+
`_table_entry` tries the whole form first and only then falls back
|
|
302
|
+
past a leading elision (fix round 2) -- see its docstring. `rhyme_key`
|
|
303
|
+
and `rhyme_keys` both call this method, so the fallback reaches them
|
|
304
|
+
for free.
|
|
305
|
+
"""
|
|
306
|
+
entry = _table_entry(word)
|
|
307
|
+
if entry is None:
|
|
308
|
+
raise MissingCapability(f"<word {word!r}>", self.lang, PHONEMES)
|
|
309
|
+
return to_phonemes(entry[1])
|
|
310
|
+
|
|
311
|
+
def is_vowel_phoneme(self, phoneme: str) -> bool:
|
|
312
|
+
return phoneme in IPA_VOWELS
|
|
313
|
+
|
|
314
|
+
def rhyme_key(self, word: str) -> str:
|
|
315
|
+
"""Phonemes from the last vowel to the end of the word.
|
|
316
|
+
|
|
317
|
+
The base definition says "from the last primary-stressed vowel", but
|
|
318
|
+
French has no lexical stress (spec D2), so the final vowel is the
|
|
319
|
+
rhyme-bearing one -- the *rime suffisante* every French prosodist
|
|
320
|
+
assumes. Feminine/masculine alternation is NOT modelled here; this
|
|
321
|
+
project's `rhyme_scheme` models it in no language, and that stays a
|
|
322
|
+
separate, recorded gap rather than something this method papers over.
|
|
323
|
+
|
|
324
|
+
Raises `MissingCapability` for a word Lexique does not carry, via
|
|
325
|
+
`phonemes()` -- a guessed rhyme is worse than an unknown one, and
|
|
326
|
+
`core.prosody.word_rhyme_keys` catches exactly that exception around
|
|
327
|
+
`rhyme_keys` and reads it as "this word is undecidable", which is what
|
|
328
|
+
makes an unknown word in a French poem non-fatal rather than an
|
|
329
|
+
uncaught crash (fix-round 1: it was `KeyError` and unhandled there).
|
|
330
|
+
"""
|
|
331
|
+
phones = self.phonemes(word)
|
|
332
|
+
for index in range(len(phones) - 1, -1, -1):
|
|
333
|
+
if self.is_vowel_phoneme(phones[index]):
|
|
334
|
+
return "".join(phones[index:])
|
|
335
|
+
return "".join(phones)
|
|
336
|
+
|
|
337
|
+
def rhyme_keys(self, word: str) -> list[str]:
|
|
338
|
+
"""Every pronunciation's rhyme key -- one, since the table holds a
|
|
339
|
+
single transcription per spelling (ADR 0034 records the homograph
|
|
340
|
+
cost that comes with that)."""
|
|
341
|
+
return [self.rhyme_key(word)]
|
|
342
|
+
|
|
343
|
+
def nouns(self) -> tuple[str, ...]:
|
|
344
|
+
return noun_list()
|
|
345
|
+
|
|
346
|
+
def noun_index(self, word: str) -> int | None:
|
|
347
|
+
return noun_positions().get(self._lemma(word))
|
|
348
|
+
|
|
349
|
+
def glosses(self, word: str) -> tuple[str, ...]:
|
|
350
|
+
"""Every sense, or nothing. Empty rather than raising, matching English."""
|
|
351
|
+
return look_up(gloss_table(), word) or ()
|
|
352
|
+
|
|
353
|
+
def graded_words(self) -> Mapping[str, int]:
|
|
354
|
+
"""A read-only view over the cached table, for the reason English gives:
|
|
355
|
+
the module function is `lru_cache`d and shared, so handing the dict out
|
|
356
|
+
would let one caller's mutation corrupt it for all the others."""
|
|
357
|
+
return MappingProxyType(graded_words())
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _lemma(word: str) -> str:
|
|
361
|
+
"""Casefold, and keep accents.
|
|
362
|
+
|
|
363
|
+
English strips to ASCII here. French must not: `fold_diacritics` would
|
|
364
|
+
collide `côte` with `cote` and `pêcheur` with `pecheur`, and ADR 0009
|
|
365
|
+
makes folding a parameter of the procedure rather than a property of the
|
|
366
|
+
lexicon. German keeps its umlauts for the same reason.
|
|
367
|
+
"""
|
|
368
|
+
return "".join(ch for ch in word.casefold() if ch.isalpha())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"counts": {
|
|
3
|
+
"glosses.txt.gz": 510973,
|
|
4
|
+
"graded_words.txt.gz": 125343,
|
|
5
|
+
"h_aspire.txt.gz": 3370,
|
|
6
|
+
"nouns.txt.gz": 44746,
|
|
7
|
+
"syllables.txt.gz": 125653,
|
|
8
|
+
"words.txt.gz": 125343
|
|
9
|
+
},
|
|
10
|
+
"generated": "2026-09-01",
|
|
11
|
+
"source": "Lexique 3.82 (lexique.org), CC BY-SA 4.0, and French Wiktionary, CC BY-SA 4.0 - see LICENSE-LEXIQUE, LICENSE-WIKTIONARY and scripts/build_lexicon.py"
|
|
12
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""French verse counts a line, not a sequence of words (spec D3).
|
|
2
|
+
|
|
3
|
+
A final mute e is a syllable before a consonant, elides before a vowel or a
|
|
4
|
+
mute h, and never counts at the end of a line. Lexique gives citation forms --
|
|
5
|
+
`femme`, `une`, `belle` and `porte` are all one syllable there and frequently
|
|
6
|
+
two in verse -- so summing them undercounts systematically.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
|
|
14
|
+
VOWELS = frozenset("aeiouyàâéèêëîïôöùûüœæ")
|
|
15
|
+
_VOWEL_RUN = re.compile(r"[aeiouyàâéèêëîïôöùûüœæ]+")
|
|
16
|
+
|
|
17
|
+
#: SAMPA consonants (Lexique's alphabet, `sampa.SAMPA_TO_IPA` minus the
|
|
18
|
+
#: vowels and the three glides j/w/8). Used only by the diérèse pass below.
|
|
19
|
+
_SAMPA_CONSONANTS = frozenset("bdfgklmnNpRsStvzZGx")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _dierese_extra(ortho: str, phon: str) -> int:
|
|
23
|
+
"""Extra syllables classical diérèse adds to Lexique's citation count.
|
|
24
|
+
|
|
25
|
+
ADR 0034. Classical French diérèse -- whether a Latin/Greek-derived `i`
|
|
26
|
+
or `u` glide (`diadème`, `division`) counts as its own syllable, as
|
|
27
|
+
against a native word's fixed glide (`pied`, `moindre`) that never does
|
|
28
|
+
-- is etymological, not spelling-derived (Task 7 brief). The rule
|
|
29
|
+
shipped here: every `j` after any consonant, single or a cluster, is an
|
|
30
|
+
extra syllable, gated on the word ending in the `-ion(s)` noun suffix
|
|
31
|
+
(the textbook diérèse site: Latin `-io`/`-ionem`) or starting with
|
|
32
|
+
`dia-`. Measured against Racine's *Mithridate* and Hugo's *Hernani*
|
|
33
|
+
(interior lines only; the harness that produced the table below was a
|
|
34
|
+
throwaway script, per spec, and is not shipped -- ADR 0034 D5 carries
|
|
35
|
+
the rule and the table, not the method), this is **Racine 89.3% ->
|
|
36
|
+
90.3%, Hugo 79.3% -> 80.2%**, against a
|
|
37
|
+
diérèse ceiling of 96.6%/84.8% -- a mechanical rule takes about 1.0 of
|
|
38
|
+
the 7.3 points a perfect per-site oracle would.
|
|
39
|
+
|
|
40
|
+
Three mechanical variants were measured on the way here, none of which
|
|
41
|
+
ships unchanged:
|
|
42
|
+
|
|
43
|
+
- a glide after a consonant CLUSTER ending in a liquid (R, l): worse
|
|
44
|
+
than no rule at all on both texts (84.5%/74.7% against the
|
|
45
|
+
89.3%/79.3% baseline);
|
|
46
|
+
- every `j` after a single consonant, excluding verb-inflection and
|
|
47
|
+
adjective endings that are reliably synérèse (`-iez`, `-ions`, `-ier`,
|
|
48
|
+
`-ien`, `-iable`, `-iant`, `-iance` and their plurals/feminines) --
|
|
49
|
+
those grammatical endings are the plurality of every "j after a single
|
|
50
|
+
consonant" site in the whole Lexique table, which is why a still
|
|
51
|
+
blunter blanket rule scored a catastrophic 49.5%/36.6%; excluding them
|
|
52
|
+
only brought it to 86.7%/70.0%, still below baseline on Hugo;
|
|
53
|
+
- `j` after a single consonant, narrowed further to the `-ion(s)` noun
|
|
54
|
+
suffix and the `dia-` prefix: the only variant that beat baseline on
|
|
55
|
+
BOTH texts, measured **as tried, with the single-consonant
|
|
56
|
+
restriction still in place, at 90.3%/80.0%**.
|
|
57
|
+
|
|
58
|
+
The `-ion(s)`/`dia-` gate is the shape that third variant kept. Its
|
|
59
|
+
single-consonant restriction was not: fix round 1 found it carried over
|
|
60
|
+
from the first, rejected variant (cluster-ending-in-a-liquid) without
|
|
61
|
+
independent justification, isolated it, and measured it as actively
|
|
62
|
+
costing 0.2 points on Hugo -- for no gain on Racine -- by wrongly
|
|
63
|
+
suppressing `factions` and `predictions`, both genuine `-ion` diérèse
|
|
64
|
+
sites whose `j` follows a `ks`/`ts` cluster. It was dropped; every `j`
|
|
65
|
+
after any consonant now counts once the gate passes, which is the
|
|
66
|
+
90.3%/80.2% above, not the 90.3%/80.0% the gated variant measured.
|
|
67
|
+
|
|
68
|
+
None of the three reached the 94% accept bar -- this is a STOP, not an
|
|
69
|
+
ACCEPT -- so it ships as the honest ceiling of a three-variant budget,
|
|
70
|
+
not a solved rule: `line_syllables`'s count stays an estimate
|
|
71
|
+
(spec D4), never dictionary-exact. `w` and `8` are left out entirely:
|
|
72
|
+
they overwhelmingly spell native `oi`/`ou`/`ui` digraphs that are never
|
|
73
|
+
split, and no variant that touched them improved on one that ignored
|
|
74
|
+
them.
|
|
75
|
+
|
|
76
|
+
Whole-branch review (2026-09-01) found an undocumented `-iation`/`-iations`
|
|
77
|
+
exclusion on the `-ion(s)` gate -- present in code, named nowhere: not
|
|
78
|
+
here, not in ADR 0034 D5, not in the plan. Re-measured against Racine and
|
|
79
|
+
Hugo interior lines with and without it: **identical to four significant
|
|
80
|
+
figures both ways, 90.3%/80.2%**, because neither corpus contains a
|
|
81
|
+
French `-iation`/`-iations` word at all (the only hits are English
|
|
82
|
+
"appreciation"/"abbreviation"/"associations" in Hugo's front matter,
|
|
83
|
+
outside any verse block). An exclusion with no measured effect and no
|
|
84
|
+
citable justification earns nothing, so it was dropped rather than
|
|
85
|
+
documented: `aviation`, `association` and `initiation` now take diérèse
|
|
86
|
+
like any other `-ion(s)` word, on the same unverified footing as the
|
|
87
|
+
rest of the gate.
|
|
88
|
+
"""
|
|
89
|
+
is_ion = ortho.endswith(("ion", "ions"))
|
|
90
|
+
if not (is_ion or ortho.startswith("dia")):
|
|
91
|
+
return 0
|
|
92
|
+
extra = 0
|
|
93
|
+
for index, char in enumerate(phon):
|
|
94
|
+
if char != "j" or index == 0 or phon[index - 1] not in _SAMPA_CONSONANTS:
|
|
95
|
+
continue
|
|
96
|
+
extra += 1
|
|
97
|
+
return extra
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#: Proclitics whose own vowel is already elided in the spelling. They carry no
|
|
101
|
+
#: syllable, but they are CONSONANTS for the word in front of them: in
|
|
102
|
+
#: "ne t'attendais" the schwa of `ne` cannot elide across the `t'`. Dropping
|
|
103
|
+
#: them from the token stream cost six points in the go/no-go prototype.
|
|
104
|
+
ELIDED_ZERO: frozenset[str] = frozenset({"l", "d", "j", "n", "m", "t", "s", "c", "qu"})
|
|
105
|
+
#: Same, but these carry syllables: `lorsqu'il` is lors-qu'il.
|
|
106
|
+
ELIDED_WORD: dict[str, str] = {
|
|
107
|
+
"lorsqu": "lorsque",
|
|
108
|
+
"puisqu": "puisque",
|
|
109
|
+
"quoiqu": "quoique",
|
|
110
|
+
"jusqu": "jusque",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#: Word-shaped tokens, keeping an internal apostrophe joined rather than
|
|
114
|
+
#: splitting on it -- the same shape as `denckring.lang.base.WORD_RE`. Fix
|
|
115
|
+
#: round 1: splitting unconditionally corrupted the 94 Lexique entries keyed
|
|
116
|
+
#: WITH an internal apostrophe (`aujourd'hui`, `prud'homme`), which are real
|
|
117
|
+
#: single words, not a proclitic plus a word -- `count_line` tries the whole
|
|
118
|
+
#: token against the table first and only falls back to splitting when that
|
|
119
|
+
#: fails (`l'ami` is not a table entry, so it still splits). Hyphens and
|
|
120
|
+
#: whitespace both fall outside `\w` in a Unicode pattern, so a compound like
|
|
121
|
+
#: "dit-elle" tokenises as two tokens without any special case for the hyphen.
|
|
122
|
+
_TOKEN_RE = re.compile(r"[^\W\d_]+(?:['’][^\W\d_]+)*") # noqa: RUF001
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def latent_schwa(ortho: str, nbsyll: int, phon: str, orthosyll: str) -> tuple[int, bool]:
|
|
126
|
+
"""(syllables excluding any mute e, whether a mute e is pending).
|
|
127
|
+
|
|
128
|
+
Lexique's SAMPA `@` is the nasal /ɑ̃/, NOT a schwa -- `dans` is `d@`.
|
|
129
|
+
Schwa is `2`, which also spells /ø/, so `de` and `deux` are both `d2` and
|
|
130
|
+
only the bare orthographic -e separates them.
|
|
131
|
+
"""
|
|
132
|
+
suffix = next((s for s in ("ent", "es", "e") if ortho.endswith(s)), None)
|
|
133
|
+
if suffix is None:
|
|
134
|
+
return nbsyll, False
|
|
135
|
+
if suffix == "e" and phon[-1:] in ("2", "°"):
|
|
136
|
+
# `le`, `je`, `que`: Lexique counted the schwa, so take it back out.
|
|
137
|
+
return max(nbsyll - 1, 0), True
|
|
138
|
+
if len(orthosyll.split("-")) > nbsyll:
|
|
139
|
+
# `bel-le` against 1 shows the mute e; `sou-vent` against 2 does not.
|
|
140
|
+
return nbsyll, True
|
|
141
|
+
if suffix == "ent":
|
|
142
|
+
# orthosyll already judged it. `vient` strips to `vi`, whose one vowel
|
|
143
|
+
# run matches nbsyll 1, so the fallback below would invent a mute e.
|
|
144
|
+
return nbsyll, False
|
|
145
|
+
# orthosyll merges the mute e after a vowel (`vie`, `joie`, `an-née`), so
|
|
146
|
+
# fall back to vowel letter-runs. `qu`/`gu` carry a silent u.
|
|
147
|
+
stripped = ortho[: -len(suffix)].replace("qu", "k").replace("gu", "g")
|
|
148
|
+
return nbsyll, len(_VOWEL_RUN.findall(stripped)) == nbsyll
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _estimate(ortho: str) -> tuple[int, bool]:
|
|
152
|
+
"""A vowel-run guess for a word Lexique does not carry.
|
|
153
|
+
|
|
154
|
+
`line_syllables` must degrade gracefully rather than raise -- unlike
|
|
155
|
+
`syllables()`/`phonemes()`, which raise `MissingCapability` for exactly
|
|
156
|
+
this case (Task 5). A real line of French routinely holds a word outside
|
|
157
|
+
Lexique's 125,653 rows, and one unknown word must not blank the count for
|
|
158
|
+
the whole line. Deliberately excludes `-ent` from the suffix check TRAP 3
|
|
159
|
+
warns about: with no `orthosyll` to consult, there is no way to tell
|
|
160
|
+
`chantent` from `vient`, so the safer default is to never invent a mute e
|
|
161
|
+
there at all.
|
|
162
|
+
"""
|
|
163
|
+
suffix = next((s for s in ("es", "e") if ortho.endswith(s) and ortho != s), None)
|
|
164
|
+
if suffix is None:
|
|
165
|
+
return max(len(_VOWEL_RUN.findall(ortho)), 1), False
|
|
166
|
+
stripped = ortho[: -len(suffix)]
|
|
167
|
+
return max(len(_VOWEL_RUN.findall(stripped)), 1), True
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _judge(ortho: str, entry: tuple[int, str, str]) -> tuple[int, bool, bool]:
|
|
171
|
+
"""(syllables excluding a pending mute e, mute e pending, judged).
|
|
172
|
+
|
|
173
|
+
A table hit's `nbsyll` is a citation fact, not a guess -- Lexique's
|
|
174
|
+
dictionary count for a known spelling is exact. What sits on top of it is
|
|
175
|
+
not: whether a mute e is pending (elides or counts depending on the next
|
|
176
|
+
word) and whether classical diérèse adds a syllable are both editorial
|
|
177
|
+
judgments the spelling alone does not settle (`extraordinaire` is five
|
|
178
|
+
syllables or six depending on the poet, D5's own example). Finding 3 of
|
|
179
|
+
the whole-branch review: spec D4 and ADR 0034 both say the French line
|
|
180
|
+
count is `exact=False`, always, and before this the flag only fired on an
|
|
181
|
+
out-of-vocabulary word -- a line entirely of known words with a pending
|
|
182
|
+
mute e or a diérèse site reported `estimated_words == 0`, confidently
|
|
183
|
+
wrong in the D4/`test_prosody_robustness`-flagged sense. `judged` is True
|
|
184
|
+
when either call was made, so `count_line` can fold it into the same
|
|
185
|
+
`estimated_words` counter it already keeps for out-of-vocabulary words.
|
|
186
|
+
"""
|
|
187
|
+
nbsyll, phon, orthosyll = entry
|
|
188
|
+
count, pending = latent_schwa(ortho, nbsyll, phon, orthosyll)
|
|
189
|
+
extra = _dierese_extra(ortho, phon)
|
|
190
|
+
return count + extra, pending, pending or extra > 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _lookup(ortho: str, table: Mapping[str, tuple[int, str, str]]) -> tuple[int, bool, bool]:
|
|
194
|
+
"""(syllables excluding a pending mute e, mute e pending, estimated).
|
|
195
|
+
|
|
196
|
+
Diérèse (Task 7, ADR 0034) is added here rather than at the caller: it is
|
|
197
|
+
a property of the word's own phonetics, the same footing as the mute-e
|
|
198
|
+
count it sits beside, and an unknown word (`_estimate`) has no `phon` to
|
|
199
|
+
judge it by, so the diérèse pass only ever runs on a table hit.
|
|
200
|
+
|
|
201
|
+
`estimated` covers two different reasons now (Finding 3): a word outside
|
|
202
|
+
Lexique altogether (`_estimate`, always estimated), and a table hit whose
|
|
203
|
+
count rests on a mute-e or diérèse judgment (`_judge`, delegated to it).
|
|
204
|
+
"""
|
|
205
|
+
entry = table.get(ortho)
|
|
206
|
+
if entry is None:
|
|
207
|
+
count, pending = _estimate(ortho)
|
|
208
|
+
return count, pending, True
|
|
209
|
+
return _judge(ortho, entry)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def starts_with_vowel(word: str, aspire: frozenset[str]) -> bool:
|
|
213
|
+
"""Whether the token that follows lets a preceding mute e elide.
|
|
214
|
+
|
|
215
|
+
A word starting with a vowel letter elides a schwa in front of it, and so
|
|
216
|
+
does a mute h -- silent, letting the vowel behind it show. An aspirated h
|
|
217
|
+
(Task 3's list) behaves as a consonant instead: "je hais" keeps `je`'s
|
|
218
|
+
schwa where "une heure" loses `une`'s.
|
|
219
|
+
"""
|
|
220
|
+
if not word:
|
|
221
|
+
return False
|
|
222
|
+
if word[0] in VOWELS:
|
|
223
|
+
return True
|
|
224
|
+
return word[0] == "h" and word not in aspire
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _split_at_apostrophe(word: str) -> tuple[str, str]:
|
|
228
|
+
"""(stem before the first apostrophe, everything after it).
|
|
229
|
+
|
|
230
|
+
Called only once the whole token has already failed a table lookup, so
|
|
231
|
+
this never runs on `aujourd'hui` or `prud'homme` -- both are single
|
|
232
|
+
Lexique entries and are used whole (fix round 1).
|
|
233
|
+
"""
|
|
234
|
+
for index, ch in enumerate(word):
|
|
235
|
+
if ch in ("'", "’"): # noqa: RUF001
|
|
236
|
+
return word[:index], word[index + 1 :]
|
|
237
|
+
return word, ""
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def count_line(
|
|
241
|
+
line: str, table: Mapping[str, tuple[int, str, str]], aspire: frozenset[str]
|
|
242
|
+
) -> tuple[int, int]:
|
|
243
|
+
"""(syllables in the line, words whose count was estimated).
|
|
244
|
+
|
|
245
|
+
"Estimated" covers three cases now, all folded into one counter (Finding
|
|
246
|
+
3 of the whole-branch review, spec D4): a word outside Lexique
|
|
247
|
+
(`_estimate`'s vowel-run guess), a word carrying a pending mute e, and a
|
|
248
|
+
word whose diérèse gate fired. The first is out-of-vocabulary; the other
|
|
249
|
+
two are in-vocabulary but their contribution to the count rests on an
|
|
250
|
+
editorial judgment the spelling does not settle -- which is exactly what
|
|
251
|
+
D4 means by "estimated, never exact." Before this fix a line built
|
|
252
|
+
entirely from known words could carry a mute-e or diérèse judgment and
|
|
253
|
+
still report `estimated_words == 0`, the confidently-wrong shape D4
|
|
254
|
+
exists to rule out.
|
|
255
|
+
|
|
256
|
+
A token that carries an internal apostrophe is tried WHOLE against the
|
|
257
|
+
table first: 94 Lexique entries are keyed with one (`aujourd'hui`,
|
|
258
|
+
`prud'homme`), and splitting them unconditionally silently returned a
|
|
259
|
+
confidently wrong count with `estimated` left at 0 -- fix round 1's
|
|
260
|
+
finding, and the failure mode this project cares most about, a wrong
|
|
261
|
+
answer reported as certain. Only a token the table does not recognise
|
|
262
|
+
whole falls back to splitting at the apostrophe and treating the stem as
|
|
263
|
+
a proclitic ("t'", "d'", "qu'") -- a zero-syllable, consonant-initial
|
|
264
|
+
token that survives in the stream rather than being dropped, which is
|
|
265
|
+
TRAP 2: dropping it would let the preceding word's schwa see the vowel
|
|
266
|
+
behind the proclitic and elide across it, which it must not.
|
|
267
|
+
|
|
268
|
+
Walks the tokens once, adding one syllable for each pending mute e whose
|
|
269
|
+
successor is consonant-initial. A pending mute e with no successor -- the
|
|
270
|
+
line's last token -- is never added, which is how "the final e never
|
|
271
|
+
counts at the end of a line" falls out of "not last" rather than needing
|
|
272
|
+
a separate rule.
|
|
273
|
+
"""
|
|
274
|
+
tokens = _TOKEN_RE.findall(line)
|
|
275
|
+
# Each entry: (syllables carried, mute e pending, was estimated, text
|
|
276
|
+
# used to judge whether the NEXT word starts with a vowel).
|
|
277
|
+
parsed: list[tuple[int, bool, bool, str]] = []
|
|
278
|
+
for token in tokens:
|
|
279
|
+
lower = token.casefold()
|
|
280
|
+
if "'" not in lower and "’" not in lower: # noqa: RUF001
|
|
281
|
+
count, pending, estimated = _lookup(lower, table)
|
|
282
|
+
parsed.append((count, pending, estimated, lower))
|
|
283
|
+
continue
|
|
284
|
+
entry = table.get(lower)
|
|
285
|
+
if entry is not None:
|
|
286
|
+
# `aujourd'hui`, `prud'homme`: a fused word, not a proclitic plus
|
|
287
|
+
# a word -- 94 Lexique entries carry an internal apostrophe.
|
|
288
|
+
count, pending, judged = _judge(lower, entry)
|
|
289
|
+
parsed.append((count, pending, judged, lower))
|
|
290
|
+
continue
|
|
291
|
+
# The whole form isn't a table entry, so it wasn't a fused word like
|
|
292
|
+
# those -- read the apostrophe as an elision boundary instead.
|
|
293
|
+
stem, rest = _split_at_apostrophe(lower)
|
|
294
|
+
if stem in ELIDED_ZERO:
|
|
295
|
+
parsed.append((0, False, False, stem))
|
|
296
|
+
elif (full := ELIDED_WORD.get(stem)) is not None and (
|
|
297
|
+
word_entry := table.get(full)
|
|
298
|
+
) is not None:
|
|
299
|
+
# `lorsqu'il` etc: already elided in the spelling, so the mute e
|
|
300
|
+
# is gone, not merely pending -- `pending` stays False regardless
|
|
301
|
+
# of what `latent_schwa` would say about `full` on its own. A
|
|
302
|
+
# diérèse site is still a live judgment here, so it alone decides
|
|
303
|
+
# `estimated` (Finding 3).
|
|
304
|
+
nbsyll, phon, orthosyll = word_entry
|
|
305
|
+
count, _pending = latent_schwa(full, nbsyll, phon, orthosyll)
|
|
306
|
+
extra = _dierese_extra(full, phon)
|
|
307
|
+
parsed.append((count + extra, False, extra > 0, stem))
|
|
308
|
+
else:
|
|
309
|
+
# An elided proclitic neither list recognises -- there is no
|
|
310
|
+
# basis for a confident zero, so flag it estimated rather than
|
|
311
|
+
# silently asserting one (fix round 1, same defect as the
|
|
312
|
+
# whole-token case above, now on the proclitic path).
|
|
313
|
+
parsed.append((0, False, True, stem))
|
|
314
|
+
if rest:
|
|
315
|
+
count, pending, estimated = _lookup(rest, table)
|
|
316
|
+
parsed.append((count, pending, estimated, rest))
|
|
317
|
+
|
|
318
|
+
total = 0
|
|
319
|
+
estimated_words = 0
|
|
320
|
+
last = len(parsed) - 1
|
|
321
|
+
for index, (count, pending, was_estimated, _text) in enumerate(parsed):
|
|
322
|
+
total += count
|
|
323
|
+
if was_estimated:
|
|
324
|
+
estimated_words += 1
|
|
325
|
+
if pending and index < last:
|
|
326
|
+
successor = parsed[index + 1][3]
|
|
327
|
+
if not starts_with_vowel(successor, aspire):
|
|
328
|
+
total += 1
|
|
329
|
+
return total, estimated_words
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Lexique 3.82's SAMPA, in the IPA this project uses everywhere else.
|
|
2
|
+
|
|
3
|
+
Lexique's alphabet is its own: `@` is the nasal /ɑ̃/ rather than a schwa, `§`
|
|
4
|
+
is /ɔ̃/, and `2`/`9` split /ø/ from /œ/. Reading `@` as a schwa costs fifteen
|
|
5
|
+
points of line accuracy and looks like a plausible one-syllable undercount, so
|
|
6
|
+
this table is tested against hand-checked words before anything reads it
|
|
7
|
+
(spec F0).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
#: Two-character sequences do not occur in Lexique's phon column, so a
|
|
13
|
+
#: character-by-character walk is complete.
|
|
14
|
+
SAMPA_TO_IPA: dict[str, str] = {
|
|
15
|
+
# Oral vowels
|
|
16
|
+
"a": "a",
|
|
17
|
+
"A": "ɑ",
|
|
18
|
+
"e": "e",
|
|
19
|
+
"E": "ɛ",
|
|
20
|
+
"i": "i",
|
|
21
|
+
"o": "o",
|
|
22
|
+
"O": "ɔ",
|
|
23
|
+
"u": "u",
|
|
24
|
+
"y": "y",
|
|
25
|
+
"2": "ø",
|
|
26
|
+
"9": "œ",
|
|
27
|
+
"°": "ə",
|
|
28
|
+
# Nasal vowels
|
|
29
|
+
"@": "ɑ̃",
|
|
30
|
+
"5": "ɛ̃",
|
|
31
|
+
"1": "œ̃",
|
|
32
|
+
"§": "ɔ̃",
|
|
33
|
+
# Glides
|
|
34
|
+
"j": "j",
|
|
35
|
+
"w": "w",
|
|
36
|
+
"8": "ɥ",
|
|
37
|
+
# Consonants
|
|
38
|
+
"b": "b",
|
|
39
|
+
"d": "d",
|
|
40
|
+
"f": "f",
|
|
41
|
+
"g": "g",
|
|
42
|
+
"k": "k",
|
|
43
|
+
"l": "l",
|
|
44
|
+
"m": "m",
|
|
45
|
+
"n": "n",
|
|
46
|
+
"N": "ɲ",
|
|
47
|
+
"p": "p",
|
|
48
|
+
"R": "ʁ",
|
|
49
|
+
"s": "s",
|
|
50
|
+
"S": "ʃ",
|
|
51
|
+
"t": "t",
|
|
52
|
+
"v": "v",
|
|
53
|
+
"z": "z",
|
|
54
|
+
"Z": "ʒ",
|
|
55
|
+
"G": "ŋ",
|
|
56
|
+
"x": "x",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#: A glide is written with a vowel symbol in some schemes but carries no
|
|
60
|
+
#: syllable of its own, so it is not here. ADR 0030 is the reason this is a
|
|
61
|
+
#: property of the transcription rather than of the phoneme string.
|
|
62
|
+
IPA_VOWELS: frozenset[str] = frozenset(
|
|
63
|
+
{"a", "ɑ", "e", "ɛ", "i", "o", "ɔ", "u", "y", "ø", "œ", "ə", "ɑ̃", "ɛ̃", "œ̃", "ɔ̃"}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def to_ipa(sampa: str) -> str:
|
|
68
|
+
"""Convert one Lexique transcription. An unmapped symbol raises.
|
|
69
|
+
|
|
70
|
+
Passing an unknown symbol through would let a scheme leak silently into a
|
|
71
|
+
syllable count, which is the defect ADR 0030 fixed twice in one day.
|
|
72
|
+
"""
|
|
73
|
+
return "".join(SAMPA_TO_IPA[ch] for ch in sampa)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def to_phonemes(sampa: str) -> list[str]:
|
|
77
|
+
"""The transcription as a list of IPA phonemes.
|
|
78
|
+
|
|
79
|
+
Lexique writes one symbol per phoneme, so the split is the walk itself and
|
|
80
|
+
no re-segmentation of the IPA string is needed -- `d@` is ["d", "ɑ̃"], and
|
|
81
|
+
the nasal stays one phoneme rather than a vowel plus a combining tilde.
|
|
82
|
+
"""
|
|
83
|
+
return [SAMPA_TO_IPA[ch] for ch in sampa]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: denckring-fr-data
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: French lexicon and prosody data for denckring: word membership, nouns, frequency bands, glosses, syllables and phonemes
|
|
5
|
+
Author-email: Dennis Senzel <dennis.senzel@posteo.com>
|
|
6
|
+
License-Expression: Apache-2.0 AND CC-BY-SA-4.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
License-File: LICENSE-LEXIQUE
|
|
9
|
+
License-File: LICENSE-WIKTIONARY
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Natural Language :: French
|
|
13
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: denckring==0.1.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# denckring-fr-data
|
|
20
|
+
|
|
21
|
+
French lexicon and prosody data for [denckring](https://github.com/senzelden/denckring):
|
|
22
|
+
word membership, an ordered noun list, frequency bands, glosses, a syllable and
|
|
23
|
+
phoneme table, and an aspirated-*h* list.
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
pip install denckring[fr]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Installing this gives the French pack `lexicon.words`, `lexicon.nouns`,
|
|
30
|
+
`lexicon.glosses`, `lexicon.graded_words`, `syllables`, `syllables.dictionary`,
|
|
31
|
+
`syllables.heuristic` and `phonemes`. The lexicon capabilities are what
|
|
32
|
+
`charade`, `semordnilap`, `word_square`, `n_plus_7`, `s_plus_7`, `tmesis`,
|
|
33
|
+
`word_ladder`, `kangaroo_word` and the two definitional rows need in order to
|
|
34
|
+
run in French — and what `apply anagram` needs in order to generate in it. The
|
|
35
|
+
prosody capabilities, plus the line-level elision rules `FrenchDataPack`
|
|
36
|
+
overrides `line_syllables` with, are what the 23 syllabic rows
|
|
37
|
+
(`alexandrine`, `syllable_count`, `rhyme_scheme`, `haiku`, `hemeling`, and the
|
|
38
|
+
rest — ADR 0034) need in order to run in French. Without this package, all of
|
|
39
|
+
it raises `MissingCapability` for `fr`, exactly as for any unmet capability.
|
|
40
|
+
French still has no lexical stress, so the 18 rows that need `stress` stay
|
|
41
|
+
blocked regardless — see ADR 0034 D3.
|
|
42
|
+
|
|
43
|
+
**125,653 syllable/phoneme rows and 3,370 aspirated-*h* entries**, alongside
|
|
44
|
+
the lexicon: 46,947 lemmas, 48,234 noun forms and a Wiktionary gloss slice.
|
|
45
|
+
Both sources are **CC BY-SA 4.0** — attribution *and* share-alike — Lexique
|
|
46
|
+
3.82 for the lexicon and the prosody table, French Wiktionary for the glosses
|
|
47
|
+
and the aspirated-*h* list. See `LICENSE-LEXIQUE` and `LICENSE-WIKTIONARY`.
|
|
48
|
+
Regenerate the vendored files with `scripts/build_lexicon.py`.
|
|
49
|
+
|
|
50
|
+
Accents are preserved rather than folded. `côte` and `cote` are different words,
|
|
51
|
+
and whether they should be treated alike is a decision ADR 0009 leaves to the
|
|
52
|
+
procedure, not to the lexicon.
|
|
53
|
+
|
|
54
|
+
The French line count is **estimated, never exact** (ADR 0034 D4): a final
|
|
55
|
+
mute *e* elides or counts depending on what follows it, and classical
|
|
56
|
+
diérèse is not decidable from spelling alone. Every syllabic report carries
|
|
57
|
+
`estimated_words` accordingly.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
denckring_fr_data/__init__.py,sha256=xYZIJzAUm8RCG4CsXQNRtu9aewtOQkrFb797ZGgUZ8Q,16141
|
|
2
|
+
denckring_fr_data/elision.py,sha256=FGoUF2iVhJvn1t1CSYneWGXBZDLlU6zbpvKLxLZhArQ,16839
|
|
3
|
+
denckring_fr_data/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
denckring_fr_data/sampa.py,sha256=o8APJPRtynzfPoailbcZ-Ag3fcP2fikMyQDabCDAU5Q,2279
|
|
5
|
+
denckring_fr_data/data/glosses.txt.gz,sha256=SVt_oCYayaOfK3EJkxihPJqhpL7umpn8ABbCXZKmWPk,15270003
|
|
6
|
+
denckring_fr_data/data/graded_words.txt.gz,sha256=r3X-d0OH05x_wpVXpatsDFJ3eU3b8-fqFm0OwYIdpa0,413181
|
|
7
|
+
denckring_fr_data/data/h_aspire.txt.gz,sha256=LX8DhJQ0GsnYKpJZZyx61mArKrW-Y4qVOwiAY4yyypk,10124
|
|
8
|
+
denckring_fr_data/data/metadata.json,sha256=qoTCjj-_wyK83ehXNfcUTAG3c25Pcf6FehQN3yy1-Uk,395
|
|
9
|
+
denckring_fr_data/data/nouns.txt.gz,sha256=HtExMdJqsFWQmUda4ckDf_aHwkvE6sxJPP1BWti7x18,126711
|
|
10
|
+
denckring_fr_data/data/syllables.txt.gz,sha256=X-H3_yulQKQGmAe9SbI9HP2u6q6QOQbSXCWToqn8YxI,1001801
|
|
11
|
+
denckring_fr_data/data/words.txt.gz,sha256=TzUtEd2knOAN54sbkG5IyFsoqRs3xYIK_29HL8VX87w,329843
|
|
12
|
+
denckring_fr_data-0.1.0.dist-info/METADATA,sha256=rpIqeMUgIeb9M-zyjKAmjGV6f0Cyhv8_SUPdlBSyHKw,2782
|
|
13
|
+
denckring_fr_data-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
14
|
+
denckring_fr_data-0.1.0.dist-info/entry_points.txt,sha256=1Bg8TfE8IxB3sS1w4XHjy757vrSflnYqCja7xDkgQTY,55
|
|
15
|
+
denckring_fr_data-0.1.0.dist-info/licenses/LICENSE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360
|
|
16
|
+
denckring_fr_data-0.1.0.dist-info/licenses/LICENSE-LEXIQUE,sha256=c-BZFUDwVcpZH5TlVEsrLKAIAte34uH4_3ULVvmH10w,879
|
|
17
|
+
denckring_fr_data-0.1.0.dist-info/licenses/LICENSE-WIKTIONARY,sha256=HnQjmLvaM6hU9xrrNknD0_U5vIyniYqR5vVkgXRRjqc,691
|
|
18
|
+
denckring_fr_data-0.1.0.dist-info/licenses/NOTICE,sha256=rSQ_EtW_ZbopAnsN1JnN42bSnJaKGzSmINsBCaDP4eY,607
|
|
19
|
+
denckring_fr_data-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
https://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The French word list, noun list and frequency bands in this distribution are
|
|
2
|
+
derived from Lexique 3.82.
|
|
3
|
+
|
|
4
|
+
New, B., Pallier, C., Ferrand, L., Matos, R. (2001). Une base de données
|
|
5
|
+
lexicales du français contemporain sur internet: LEXIQUE. L'Année
|
|
6
|
+
Psychologique, 101, 447-462.
|
|
7
|
+
http://www.lexique.org
|
|
8
|
+
|
|
9
|
+
Lexique is licensed under the Creative Commons Attribution-ShareAlike 4.0
|
|
10
|
+
International licence, as stated in the README bundled with Lexique382.zip:
|
|
11
|
+
|
|
12
|
+
https://creativecommons.org/licenses/by-sa/4.0/
|
|
13
|
+
|
|
14
|
+
Attribution: Boris New and Christophe Pallier, and the Lexique contributors.
|
|
15
|
+
|
|
16
|
+
Share-alike: the derived data files in this distribution
|
|
17
|
+
(`src/denckring_fr_data/data/`) are offered under the same CC BY-SA 4.0 licence.
|
|
18
|
+
The code in this distribution is Apache-2.0 and is not a derivative of the data.
|
|
19
|
+
|
|
20
|
+
Regenerate the vendored files with scripts/build_lexicon.py.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
The French glosses in this distribution are derived from French Wiktionary
|
|
2
|
+
(https://fr.wiktionary.org), specifically from the
|
|
3
|
+
`frwiktionary-latest-pages-articles` XML dump published by the Wikimedia
|
|
4
|
+
Foundation.
|
|
5
|
+
|
|
6
|
+
Wiktionary text is licensed under the Creative Commons
|
|
7
|
+
Attribution-ShareAlike 4.0 International licence:
|
|
8
|
+
|
|
9
|
+
https://creativecommons.org/licenses/by-sa/4.0/
|
|
10
|
+
|
|
11
|
+
Attribution: the French Wiktionary contributors. Page histories, which name
|
|
12
|
+
them, are at https://fr.wiktionary.org/wiki/<headword> under "Historique".
|
|
13
|
+
|
|
14
|
+
Share-alike: the derived data files in this distribution are offered under the
|
|
15
|
+
same CC BY-SA 4.0 licence.
|
|
16
|
+
|
|
17
|
+
Regenerate the vendored files with scripts/build_lexicon.py.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
denckring-fr-data
|
|
2
|
+
Copyright 2026 senzelden
|
|
3
|
+
|
|
4
|
+
The code in this distribution is licensed under the Apache License, Version 2.0.
|
|
5
|
+
See LICENSE.
|
|
6
|
+
|
|
7
|
+
The data in this distribution comes from two sources, both licensed under the
|
|
8
|
+
Creative Commons Attribution-ShareAlike 4.0 International licence:
|
|
9
|
+
|
|
10
|
+
- Lexique 3.82 (lexique.org) — see LICENSE-LEXIQUE
|
|
11
|
+
- French Wiktionary — see LICENSE-WIKTIONARY
|
|
12
|
+
|
|
13
|
+
Both impose attribution and share-alike. That they agree is why this is one
|
|
14
|
+
distribution where German needs two: ADR 0013 quarantines a data licence per
|
|
15
|
+
distribution, and there is only one licence here to quarantine.
|