g | x | w | all
Bytes Lang Time Link
109Python 2241020T203802ZArnauld
nanWolfram Language Mathematica241019T001514ZGreg Mar
047Jelly241021T173904ZJonathan
111Google Sheets241019T093002Zdoubleun
097Excel ms365241021T133026ZJvdV
109JavaScript Node.js241018T105253ZArnauld
276Python 2241018T115812Zsquarero
086Charcoal241018T204422ZNeil
06905AB1E241018T131309ZKevin Cr
137Retina 0.8.2241018T115335ZNeil

Python 2, 109 bytes

Since my Python golfing skills are close to zero, this can probably be improved.

lambda s:sum(ord("LML\RBDLHLKpEJOL@XFd(LNAL~GIC"[hash(p)%508%298%30])^76for p in s.split("-"))+56*(s[7:]>"v")

Try it online!

Method

This is similar to my JS answer with a simpler (but longer) fix for the quatre-vingt(s) special case. We just add \$56\$ if the 8th character of the input string is a 'v', which gives:

$$4+20+n+56=80+n$$

The extra bytes introduced with this explicit test are offset by the terser Python syntax for the lookup code.

Wolfram Language (Mathematica), soixante-neuf octets

Interpreter["SemanticNumber"]@TextTranslation[#,"French"->"English"]&

Just to prove that there's always a builtin....

Jelly, 47 bytes

⁵Ḋ×;⁴ݤ“⁽²ṣỴȮ⁽ɱ?ẸỤ⁵’œ?ṭ“Ç+Zḷ’
ḟ”sṣ”-¢ḥⱮ60Ṗẹ¥¦4S

A monadic Link that accepts one of the \$101\$ lists of characters and yields the integer it represents.

Try it online! Or see all of them.

How?

Evaluate the '-' delimited parts by hashing. Convert "et" (as well as "zero") to \$0\$, and convert any leading \$4\$ found for "quatre" to \$60\$, then sum these values.

⁵Ḋ×;⁴ݤ“⁽²ṣỴȮ⁽ɱ?ẸỤ⁵’œ?ṭ“Ç+Zḷ’ - Helper Link, GetSeedAndDomain: no arguments
⁵                             - Set the left argument to Ten -> 10
 Ḋ                            - dequeue -> [2,3,4,5,6,7,8,9,10]
  ×                           - multiply by {10} -> [20,30,40,50,60,70,80,90,100]
      ¤                       - nilad followed by links as a nilad:
    ⁴                         -   16
     Ż                        -   zero-range -> [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
   ;                          - concatenate
                                 -> DomainValues = [20,30,40,50,60,70,80,90,100,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
       “⁽²ṣỴȮ⁽ɱ?ẸỤ⁵’          - base-250 number -> 135924907825578762729734134
                    œ?        - Permutation of {DomainValues} at {that} lexicographic index
                                  -> Domain = [100,11,30,12,5,70,2,60,50,4,16,10,80,13,7,20,3,9,14,8,1,6,15,90,0,40]
                       “Ç+Zḷ’ - base-250 number -> Seed = 237147969
                      ṭ       - {Domain} tack to {Seed}
                                  -> [237147969, [100,11,30,12,5,70,2,60,50,4,16,10,80,13,7,20,3,9,14,8,1,6,15,90,0,40]]

ḟ”sṣ”-¢ḥⱮ60Ṗẹ¥¦4S - Main Link, Translate: list of characters, S
ḟ”s               - filter out 's' characters (aligns "vingts" and "vingt")
   ṣ”-            - split at '-' characters -> Parts
      ¢           - call the Helper Link (above) -> SeedAndDomain
       ḥⱮ         - map across {Parts} performing Jelly's hash with {SeedAndDomain} -> HashedValues
              ¦   - sparse application...
             ¥ 4  - ...to indices: last two links as a dyad - F(HashedValues, 4):
           Ṗ      -                  pop (remove the rightmost part)
            ẹ     -                  indices of {4} -> [1] if representing 80-99
                                                    or [] otherwise
         60       - ...action: replace with 60
                S - sum

Note that the Domain includes three unused values - \$70\$, \$80\$, and \$90\$ - so improvement might be possible with a much higher Seed (“Ç+Zḷ’ \$= 237147969\$), although there would also be some overhead constructing the smaller list of DomainValues since \$100\$ is needed.

Google Sheets, 111 bytes 231 bytes

=match("*"&A1&"*",map(sequence(101),lambda(n,substitute(googletranslate(bahttext(n-1),"th","fr")," ","-"))),)-1

Uses the built-in bahttext() to convert numbers 0–100 to currency expressions in Thai so 99 gets "เก้าสิบเก้าบาทถ้วน". Then applies googletranslate() to get those expressions in French, and replaces spaces with dashes. Finally, finds the index of the input in the array thus created. Note that the formula uses the correct accented spelling of "zéro".

screenshot3

The screenshot shows Thai currency expressions and their French translations, but those columns are for demonstration only. Thanks to jdt and Klumpy7.

Here's an "honest" version that doesn't use built-ins (227 bytes):

=let(k,split("u0d0t0q0c0s0se0h0n0di0o0do0tre0quato0qui0sei0v0tren0quar0cinqu0so00b00ce",0,,),sum(map(split(regexreplace(A1,".+-v","b"),"-"),lambda(p,max(ifna(sort(hlookup(left(p,len(k)),{k;column(A:P),10*column(B:J)},2,))))))))

The same in JavaScript V8 (243 bytes, non-competing):

(t,k='0u0d0t0q0c0s0se0h0n0di0o0do0tre0quato0qui0sei0v0tren0quar0cinqu0so0b0ce'.split(0),v=[...Array(17).keys(),20,30,40,50,60,80,100])=>t.replace(/.+-v/,'b').split('-').map(p=>v.findLast((_,i)=>p.match(RegExp('^'+k[i])))||0).reduce((a,c)=>a+c)

Try it online (with findLast() polyfill.)

Excel ms365, 97 bytes

=XMATCH(A1,LOWER(REGEXREPLACE(TRANSLATE(BAHTTEXT(ROW(1:101)-1),,"fr"),"(é)| ","${1:+e:-}")),3)-1

This is making use of conditional string replacement through the PCRE2 engine available with REGEXREPLACE() and the new option in XMATCH() to match through a regular expression.

JavaScript (Node.js), 109 bytes

s=>s.replace(/(-v)?\w+/g,p=>t+=Buffer(`EX\\LJN@LAK(ROLdFMHDI~LLLpCGBL`)[parseInt(p,36)%847%272%30]^76,t=0)&&t

Try it online!

Method

The value of most French numbers in \$[0\dots100]\$ is the sum of the values of all parts, e.g. soixante-dix-sept is \$60+10+7=77\$.

So the basic idea is to isolate each word, parse it as base 36, apply some formula to get an index into a lookup ASCII string, get its value XOR'ed with some constant to avoid problematic characters, and eventually return the sum of everything.

The only exception is for quatre-vingts and all numbers of the form quatre-vingt-xxx where quatre-vingt(s) has to be interpreted as \$4\times20\$ rather than \$4+20\$.

We can handle this special case by matching the - when it precedes a v, thus turning -vingt and -vingts into negative values (in base 36) pointing out of the lookup table. By choosing \$76\$ for the XOR operation(*), they are evaluated to this value and we get the expected sum \$4+76=80\$.

(*) By a happy coincidence, this value takes us to the highest printable ASCII code \$126\$ (for \$50\$ XOR \$76\$) without exceeding it.

Lookup table

The modulo chain and the corresponding lookup table were found by running an exhaustive brute-force search for the pattern % m0 % m1 % m2 with:

$$m2 < m1 < m0 < 1000$$

applied to the 24 keys of the following dictionary parsed in base 36:

{"et":0,"zero":0,"un":1,"deux":2,"trois":3,"quatre":4,"cinq":5,"six":6,"sept":7,"huit":8,"neuf":9,"dix":10,"onze":11,"douze":12,"treize":13,"quatorze":14,"quinze":15,"seize":16,"vingt":20,"trente":30,"quarante":40,"cinquante":50,"soixante":60,"cent":100}

See the best solution

Commented

s =>                   // s = input string
s.replace(             // replace in s:
  /(-v)?\w+/g,         //   look for words, optionally preceded by "-v"
  p =>                 //   for each part p,
  t +=                 //   add to t:
    Buffer(            //     the ASCII code of the character
      `EX...BL`        //     from the lookup string
    )[                 //     at the position obtained by:
      parseInt(p, 36)  //       parsing p in base 36
      % 847 % 272 % 30 //       and reducing it with this modulo chain
    ]                  //
    ^ 76,              //     XOR'ed with 76
  t = 0                //   start with t = 0
)                      // end of replace()
&& t                   // return t

Python 2,  324   302   278  276 bytes

-22 bytes by shrinking the multiples-of-10-ish prefixes.
-22 bytes by Lucenaposition.
-2 bytes by using the enumerate(iterable, start) form.
-2 bytes by Lucenaposition.

lambda x:g(x)[0]+max([0]+[i for i,s in E('er un ux is at nq si ep h f di on uz rei rz qui sei -s -h -n'.split())if s in g(x)[1].strip('-')])
g=lambda x:max([(0,x)]+[(i*10,x[x.find('-')+i/8*7:]*('-'in x))for i,p in E('v en ar nqu xa F -v F ce'.split(),2)if p in x])
E=enumerate

Try it online!

Charcoal, 86 bytes

≔⁻S-θ≔I⁺…¹¦¹⁷×χ⊕⪪1234559¹υF⪪”↶↖↑﹪ς4EI;�ζ⊗«JKê'″.﹪⧴∨(WL4{Z=≔M⟲´←<⦄◧¦↗↥1⌊f↶” ≔⪫⪪θι⊟υθIΣθ

Try it online! Link is to verbose version of code. Explanation:

≔⁻S-θ

Remove -s from the input. This makes the lookup string of replacements more compressible. (It also works around a behaviour change on ATO which would otherwise interpret them as negative numbers in Sum.)

≔I⁺…¹¦¹⁷×χ⊕⪪1234559¹υ

Make a list of the numbers 1..16, 20, 30, 40, 50, 60, 60, 100. (Note that the Cast is not necessary on ATO as Join automatically casts its second parameter to string.)

F⪪”...” 

Loop through a compressed list of substrings ce ... un. (See my Retina answer.)

≔⪫⪪θι⊟υθ

Replace each substring with its "value".

IΣθ

Output the total value.

05AB1E, 69 bytes

.•2{ε8∍'*.rΛÅËD/Ƶ.<”g×δ€À¦„QĀmÕð'-:•l×{ñrÄ•4в£16L6YŸT*Ć«тª{R:A'-ª¡þO

Port of @Neil's Retina answer, so make sure to upvote that answer as well!

Try it online or verify all test cases.

Explanation:

.•2{ε8∍'*.rΛÅËD/Ƶ.<”g×δ€À¦„QĀmÕ
              '# Push compressed string "cee vsonquarengseiinzrzreiuzondinehpsinqreoiuxun"
  ð'-:        '# Replace the space with a "-"
•l×{ñrÄ•       # Push compressed integer 51582484503210
  4в           # Convert it to base-4 as list: [2,3,2,3,2,2,1,3,3,2,3,2,2,2,2,1,1,2,2,2,2,2,2]
    £          # Split the string into parts of those sizes
16L            # Push a list in the range [1,16]
   6YŸ         # Push a list in the range [6,2]: [6,5,4,3,2]
      T*       # Multiply each by 10: [60,50,40,30,20]
        Ć      # Enclose, append its own head: [60,50,40,30,20,60]
         «     # Merge the two lists together
          тª   # Also add an 100
            {R # Sort it in descending order
:              # Replace all substrings in the (implicit) input with those integers
 A             # Push the lowercase alphabet
  '-ª         '# Convert it to a list of characters, and append "-"
     ¡         # Split the string on those characters
      þ        # Remove all empty strings by only keeping numbers
       O       # Sum those integers together
               # (after which the result is output implicitly)

See this 05AB1E tip of mine (sections How to compress strings not part of the dictionary?, How to compress large integers?, and How to compress integer lists?) to understand why .•2{ε8∍'*.rΛÅËD/Ƶ.<”g×δ€À¦„QĀmÕ is "cee vsonquarengseiinzrzreiuzondinehpsinqreoiuxun"; •l×{ñrÄ• is 51582484503210; and •l×{ñrÄ•4в is [2,3,2,3,2,2,1,3,3,2,3,2,2,2,2,1,1,2,2,2,2,2,2].

Retina 0.8.2, 137 bytes

ce
100
e-v|so
60
nqu
50
ar
40
en
30
g
20
sei
16
inz
15
rz
14
rei
13
uz
12
on
11
di
10
ne
9
h
8
p
7
si
6
nq
5
re
4
oi
3
ux
2
un
1
\d+
$*
1

Try it online! Link includes test cases. Explanation: "Unique" substrings of each number are replaced by their value, which is then summed. (Some substrings depend on earlier replacements.) The exception is for quatre-vingt which is replaced by quatr60in20t thus totalling 80. I chose the substrings by hand so they may not be optimal.