12 · Transfer-Based MT / SYSTRAN Lineage#

Dependencies: stdlib only
Runtime: < 1 min CPU

Chapter stub — implementation coming in a future commit.

Time-machine sentences#

Every chapter runs the same 20 held-out test sentences so the quality arc across chapters is directly comparable.

Open In Colab

# This chapter is **stdlib only** — nothing to install.
# It runs on any Python 3.8+ with no pip dependencies.

12 · Transfer-Based MT — The SYSTRAN Lineage#

Transfer-based MT separates translation into three stages:

  1. Analysis — parse source into a source-language representation

  2. Transfer — map source structure to target structure

  3. Generation — realise target string from target representation

SYSTRAN (1968) pioneered this approach and powered early Google Translate until 2007.

1 · The three-stage pipeline#

LEXICON = {
    "a":{"fr":"un","pos":"DET"},"an":{"fr":"un","pos":"DET"},
    "the":{"fr":"le","pos":"DET"},
    "man":{"fr":"homme","pos":"N","g":"m"},"men":{"fr":"hommes","pos":"N","g":"m"},
    "woman":{"fr":"femme","pos":"N","g":"f"},
    "dog":{"fr":"chien","pos":"N","g":"m"},"cat":{"fr":"chat","pos":"N","g":"m"},
    "park":{"fr":"parc","pos":"N","g":"m"},"book":{"fr":"livre","pos":"N","g":"m"},
    "coat":{"fr":"manteau","pos":"N","g":"m"},"ball":{"fr":"balle","pos":"N","g":"f"},
    "lake":{"fr":"lac","pos":"N","g":"m"},"boat":{"fr":"bateau","pos":"N","g":"m"},
    "bench":{"fr":"banc","pos":"N","g":"m"},"musician":{"fr":"musicien","pos":"N","g":"m"},
    "violin":{"fr":"violon","pos":"N","g":"m"},"road":{"fr":"route","pos":"N","g":"f"},
    "ladder":{"fr":"echelle","pos":"N","g":"f"},"field":{"fr":"champ","pos":"N","g":"m"},
    "newspaper":{"fr":"journal","pos":"N","g":"m"},"wall":{"fr":"mur","pos":"N","g":"m"},
    "kitchen":{"fr":"cuisine","pos":"N","g":"f"},"windowsill":{"fr":"rebord","pos":"N","g":"m"},
    "cafe":{"fr":"cafe","pos":"N","g":"m"},"pond":{"fr":"etang","pos":"N","g":"m"},
    "beach":{"fr":"plage","pos":"N","g":"f"},"stairs":{"fr":"escaliers","pos":"N","g":"m"},
    "fountain":{"fr":"fontaine","pos":"N","g":"f"},"street":{"fr":"rue","pos":"N","g":"f"},
    "children":{"fr":"enfants","pos":"N","g":"m"},"tourists":{"fr":"touristes","pos":"N","g":"m"},
    "workers":{"fr":"ouvriers","pos":"N","g":"m"},"firefighters":{"fr":"pompiers","pos":"N","g":"m"},
    "flowers":{"fr":"fleurs","pos":"N","g":"f"},"groceries":{"fr":"courses","pos":"N","g":"f"},
    "photographs":{"fr":"photos","pos":"N","g":"f"},"ducks":{"fr":"canards","pos":"N","g":"m"},
    "people":{"fr":"personnes","pos":"N","g":"f"},"stop":{"fr":"arret","pos":"N","g":"m"},
    "group":{"fr":"groupe","pos":"N","g":"m"},"couple":{"fr":"couple","pos":"N","g":"m"},
    "chef":{"fr":"chef","pos":"N","g":"m"},"food":{"fr":"nourriture","pos":"N","g":"f"},
    "boy":{"fr":"garcon","pos":"N","g":"m"},"girl":{"fr":"fille","pos":"N","g":"f"},
    "football":{"fr":"ballon","pos":"N","g":"m"},"rain":{"fr":"pluie","pos":"N","g":"f"},
    "chess":{"fr":"echecs","pos":"N","g":"m"},"flight":{"fr":"volee","pos":"N","g":"f"},
    "bus":{"fr":"bus","pos":"N","g":"m"},"bicycle":{"fr":"velo","pos":"N","g":"m"},
    "cyclist":{"fr":"cycliste","pos":"N","g":"m"},"staircase":{"fr":"escalier","pos":"N","g":"m"},
    "walk":{"fr":"marche","pos":"V"},"walking":{"fr":"marche","pos":"V"},
    "play":{"fr":"joue","pos":"V"},"playing":{"fr":"joue","pos":"V"},
    "read":{"fr":"lit","pos":"V"},"reading":{"fr":"lit","pos":"V"},
    "wait":{"fr":"attendent","pos":"V"},"waiting":{"fr":"attendent","pos":"V"},
    "ride":{"fr":"roule","pos":"V"},"riding":{"fr":"roule","pos":"V"},
    "chase":{"fr":"poursuit","pos":"V"},"chasing":{"fr":"poursuit","pos":"V"},
    "take":{"fr":"prennent","pos":"V"},"taking":{"fr":"prennent","pos":"V"},
    "feed":{"fr":"nourrit","pos":"V"},"feeding":{"fr":"nourrit","pos":"V"},
    "dance":{"fr":"dansent","pos":"V"},"dancing":{"fr":"dansent","pos":"V"},
    "sleep":{"fr":"dort","pos":"V"},"sleeping":{"fr":"dort","pos":"V"},
    "sail":{"fr":"navigue","pos":"V"},"sailing":{"fr":"navigue","pos":"V"},
    "climb":{"fr":"grimpent","pos":"V"},"climbing":{"fr":"grimpent","pos":"V"},
    "repair":{"fr":"reparent","pos":"V"},"repairing":{"fr":"reparent","pos":"V"},
    "carry":{"fr":"porte","pos":"V"},"carrying":{"fr":"porte","pos":"V"},
    "kick":{"fr":"donne un coup","pos":"V"},"kicking":{"fr":"donne un coup","pos":"V"},
    "sit":{"fr":"est assis","pos":"V"},"sitting":{"fr":"est assis","pos":"V"},
    "run":{"fr":"courent","pos":"V"},"running":{"fr":"courent","pos":"V"},
    "prepare":{"fr":"prepare","pos":"V"},"preparing":{"fr":"prepare","pos":"V"},
    "is":{"fr":"est","pos":"AUX"},"are":{"fr":"sont","pos":"AUX"},
    "red":{"fr":"rouge","pos":"ADJ"},"warm":{"fr":"chaud","pos":"ADJ"},
    "small":{"fr":"petit","pos":"ADJ"},"tall":{"fr":"grand","pos":"ADJ"},
    "outdoor":{"fr":"exterieur","pos":"ADJ"},"old":{"fr":"vieux","pos":"ADJ"},
    "young":{"fr":"jeune","pos":"ADJ"},"crowded":{"fr":"animee","pos":"ADJ"},
    "calm":{"fr":"calme","pos":"ADJ"},"empty":{"fr":"vide","pos":"ADJ"},
    "in":{"fr":"dans","pos":"PREP"},"on":{"fr":"sur","pos":"PREP"},
    "near":{"fr":"pres de","pos":"PREP"},"through":{"fr":"dans","pos":"PREP"},
    "by":{"fr":"au bord de","pos":"PREP"},"at":{"fr":"a","pos":"PREP"},
    "up":{"fr":"dans","pos":"PREP"},"against":{"fr":"contre","pos":"PREP"},
    "of":{"fr":"de","pos":"PREP"},"his":{"fr":"son","pos":"DET"},
    "her":{"fr":"ses","pos":"DET"},"their":{"fr":"leur","pos":"DET"},
    "two":{"fr":"deux","pos":"NUM"},"several":{"fr":"plusieurs","pos":"DET"},
    "an":{"fr":"un","pos":"DET"},"a":{"fr":"un","pos":"DET"},
}

def analyse(sentence):
    tokens = sentence.rstrip(" .").lower().split()
    return [{"src": tok, **LEXICON.get(tok, {"fr": tok, "pos": "UNK"})}
            for tok in tokens]

def transfer(analysis):
    # SYSTRAN key insight: EN DET+ADJ+N -> FR DET+N+ADJ
    result, i = [], 0
    while i < len(analysis):
        tok = analysis[i]
        if (tok["pos"] in ("DET","NUM")
                and i+2 < len(analysis)
                and analysis[i+1]["pos"] == "ADJ"
                and analysis[i+2]["pos"] == "N"):
            result += [analysis[i], analysis[i+2], analysis[i+1]]
            i += 3
        else:
            result.append(tok)
            i += 1
    return result

def generate(transferred):
    return " ".join(t["fr"] for t in transferred).capitalize() + "."

def translate_en_fr(sentence):
    return generate(transfer(analyse(sentence)))

demo = [
    "A man is walking his dog in the park .",
    "A woman in a red coat is reading a book .",
    "A small boat is sailing on a calm lake .",
]
print("Transfer-Based MT (EN->FR) -- SYSTRAN-style")
print("=" * 55)
for s in demo:
    print(f"  EN: {s}")
    print(f"  FR: {translate_en_fr(s)}")
    print()

2 · Transfer rules — adjective inversion (EN DET+ADJ+N → FR DET+N+ADJ)#

adjective_tests = [
    "A red coat .", "A warm windowsill .", "A calm lake .",
    "A small boat .", "A crowded street .", "A tall ladder .",
]
print("EN (DET ADJ NOUN) -> FR (DET NOUN ADJ)")
print("-" * 50)
for en in adjective_tests:
    print(f"  {en:28s} -> {translate_en_fr(en)}")

3 · SYSTRAN’s modular architecture#

stages = [
    ("Morphological Analysis", "Decompose words; assign POS candidates"),
    ("Syntactic Analysis",     "Build dependency tree; resolve POS ambiguity"),
    ("Lexical Transfer",       "Replace src words with target equivalents"),
    ("Syntactic Transfer",     "Reordering: adjective placement, verb structure"),
    ("Morphological Generation","Inflect: gender, number, tense agreement"),
    ("Orthographic Generation","Capitalization, spacing, punctuation"),
]
print("SYSTRAN Pipeline (circa 1970s)")
print("=" * 55)
for i, (s, d) in enumerate(stages, 1):
    print(f"  {i}. {s}\n     {d}")
print("\nEach stage = thousands of hand-coded rules per language pair.")
print("Advantage: predictable, debuggable, no training data needed.")
print("Disadvantage: each new pair requires full re-engineering.")

4 · Time-machine#

TIME_MACHINE_EN = [
    "A man is walking his dog in the park .",
    "Two children are playing near a fountain .",
    "A woman in a red coat is reading a book .",
    "Several people are waiting at a bus stop .",
    "A cyclist is riding through a crowded street .",
    "A dog is chasing a ball on the beach .",
    "A group of tourists is taking photographs .",
    "A young girl is feeding ducks by a pond .",
    "Two men are playing chess in a cafe .",
    "A street musician is playing the violin .",
    "Children are running through a field of flowers .",
    "An old man is sitting on a bench reading a newspaper .",
    "A woman is carrying groceries up a flight of stairs .",
    "A boy is kicking a football against a wall .",
    "A couple is dancing on an empty street .",
    "A chef is preparing food in an outdoor kitchen .",
    "A cat is sleeping on a warm windowsill .",
    "Workers are repairing a road in the rain .",
    "A small boat is sailing on a calm lake .",
    "Firefighters are climbing a tall ladder .",
]
print("=== Chapter 12 * Time-Machine: Transfer-Based MT (EN->FR) ===")
print()
for s in TIME_MACHINE_EN:
    print(f"  EN: {s}")
    print(f"  FR: {translate_en_fr(s)}")
    print()
print("Real SYSTRAN used 50k-word dictionaries + hundreds of grammar rules per pair.")