Alphabetic transcription (English ⇄ Greek characters)

Hello,
Sometimes when I need to write in Greek I keep writing just to realize that I haven't switch the keyboard layout to Greek, but I write in English and sometimes the opposite. So, I would like to ask you if there is a way to select all what I have already written (usually not more than 20 characters including spacing) and replace the letters with the other language's letters by simulating the same keystrokes by typing.
For example
I write βοοκ instead of book
and
I write Kalhmera instead of Καλημερα
I don't know if that's possible...
Thanks in advance!

One approach might be to create a map of corresponding characters, and a macro which copied selected text, translated character by character and then repasted.

In the draft below, I've made a first guess at the mappings, but you would need to check both dictionaries below – enGk, and its twin gkEn:

selected text Hellenized.kmmacros (9.6 KB)

1 Like

Updated JS:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    const gkEn = {
        "α": "a",
        "β": "b",
        "γ": "g",
        "δ": "d",
        "ε": "e",
        "ζ": "z",
        "η": "h",
        "θ": "u",
        "ι": "i",
        "κ": "k",
        "λ": "l",
        "μ": "m",
        "ν": "n",
        "ξ": "x",
        "ο": "o",
        "π": "p",
        "ρ": "r",
        "σ": "s",
        "ς": "s",
        "τ": "t",
        "υ": "u",
        "φ": "f",
        "χ": "x",
        "ψ": "c",
        "ω": "v",
        "Α": "A",
        "Β": "B",
        "Γ": "G",
        "Δ": "D",
        "Ε": "E",
        "Ζ": "Z",
        "Η": "H",
        "Θ": "U",
        "Ι": "I",
        "Κ": "K",
        "Λ": "L",
        "Μ": "M",
        "Ν": "N",
        "Ξ": "X",
        "Ο": "O",
        "Π": "P",
        "Ρ": "R",
        "Σ": "S",
        "Τ": "T",
        "Υ": "Y",
        "Φ": "F",
        "Χ": "X",
        "Ψ": "C",
        "Ω": "V"
    };

    const enGk = {
        "a": "α",
        "b": "β",
        "g": "γ",
        "d": "δ",
        "e": "ε",
        "z": "ζ",
        "h": "η",
        "u": "υ",
        "i": "ι",
        "k": "κ",
        "l": "λ",
        "m": "μ",
        "n": "ν",
        "x": "χ",
        "o": "ο",
        "p": "π",
        "r": "ρ",
        "s": "ς",
        "t": "τ",
        "f": "φ",
        "c": "ψ",
        "v": "ω",
        "A": "Α",
        "B": "Β",
        "G": "Γ",
        "D": "Δ",
        "E": "Ε",
        "Z": "Ζ",
        "H": "Η",
        "U": "Θ",
        "I": "Ι",
        "K": "Κ",
        "L": "Λ",
        "M": "Μ",
        "N": "Ν",
        "X": "Χ",
        "O": "Ο",
        "P": "Π",
        "R": "Ρ",
        "S": "Σ",
        "T": "Τ",
        "Y": "Υ",
        "F": "Φ",
        "C": "Ψ",
        "V": "Ω"
    };

    const main = () => {
        const
            kme = Application("Keyboard Maestro Engine"),
            kmVar = kme.getvariable,
            dict = Boolean(parseInt(kmVar("toGreek"), 10)) ? (
                enGk
            ) : gkEn;

        return either(
            () => ""
        )(
            cs => cs.join("")
        )(
            bindLR(
                clipTextLR()
            )(
                clipText => Right(
                    [...clipText].map(
                        c => dict[c] || c
                    )
                )
            )
        );
    };

    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = lr =>
        // Bind operator for the Either option type.
        // If lr has a Left value then lr unchanged,
        // otherwise the function mf applied to the
        // Right value in lr.
        mf => "Left" in lr ? (
            lr
        ) : mf(lr.Right);

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        // Either a message, (if no clip text is found),
        // or the string contents of the clipboard.
        const
            v = ObjC.unwrap(
                $.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString)
            );

        return Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left("No utf8-plain-text found in clipboard.");
    };

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => "Left" in e ? (
            fl(e.Left)
        ) : fr(e.Right);

    // MAIN ---
    return main();
})();
1 Like

Works great, thank you! Although I didn’t get the difference between the two JavaScripts…
Is there any chance that it recognizes the language (or the keyboard’s layout) and trigger the right “translate”?

1 Like

Trivial difference, one just fractionally more efficient than the other.

recognizes the language (or the keyboard’s layout) and trigger the right “translate”?

Is that what you are after ?

Approaching family supper here, but I can look at that on Saturday evening.

(In the meanwhile, could you check the mappings to see that there are no glitches ?)

1 Like

Ohh, I see!

Yes! I couldn’t find a way to check the spelling or the keyboard’s layout to decide the correct “translation”, so I can trigger both with the same keystroke.

I am already trying out the macro you posted! Thanks again! I will wait for your response, when you have time! I am not in hurry!

1 Like

As for the substitution part, you may be able to use the "tr" command which can substitute one set of characters for another all in a single command, like this:

I am not sure if you can put Greek characters into that command; you would have to test that yourself.

Thank you for your suggestion! It is very helpful. I will try it, although the solution from ComplexPoint works great!

If you would be so kind, let me know if it works on other character sets.

Took a quick look – this version aims to transcribe alphabetically:

  • to English if the clipboard contains any Greek characters,
  • otherwise to Greek.

selected text toggled Anglo ⇄ Greek letters.kmmacros (9.8 KB)

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    // Rob Trew @2022
    // Converting clipboard characters EN ⇄ Greek

    const gkEn = {
        "α": "a",
        "β": "b",
        "γ": "g",
        "δ": "d",
        "ε": "e",
        "ζ": "z",
        "η": "h",
        "θ": "u",
        "ι": "i",
        "κ": "k",
        "λ": "l",
        "μ": "m",
        "ν": "n",
        "ξ": "x",
        "ο": "o",
        "π": "p",
        "ρ": "r",
        "σ": "s",
        "ς": "w",
        "τ": "t",
        "υ": "u",
        "φ": "f",
        "χ": "x",
        "ψ": "c",
        "ω": "v",
        "Α": "A",
        "Β": "B",
        "Γ": "G",
        "Δ": "D",
        "Ε": "E",
        "Ζ": "Z",
        "Η": "H",
        "Θ": "U",
        "Ι": "I",
        "Κ": "K",
        "Λ": "L",
        "Μ": "M",
        "Ν": "N",
        "Ξ": "X",
        "Ο": "O",
        "Π": "P",
        "Ρ": "R",
        "Σ": "S",
        "Τ": "T",
        "Υ": "Y",
        "Φ": "F",
        "Χ": "X",
        "Ψ": "C",
        "Ω": "V"
    };

    const enGk = {
        "a": "α",
        "b": "β",
        "g": "γ",
        "d": "δ",
        "e": "ε",
        "z": "ζ",
        "h": "η",
        "u": "υ",
        "i": "ι",
        "k": "κ",
        "l": "λ",
        "m": "μ",
        "n": "ν",
        "x": "χ",
        "o": "ο",
        "p": "π",
        "r": "ρ",
        "s": "σ",
        "w": "ς",
        "t": "τ",
        "f": "φ",
        "c": "ψ",
        "v": "ω",
        "A": "Α",
        "B": "Β",
        "G": "Γ",
        "D": "Δ",
        "E": "Ε",
        "Z": "Ζ",
        "H": "Η",
        "U": "Θ",
        "I": "Ι",
        "K": "Κ",
        "L": "Λ",
        "M": "Μ",
        "N": "Ν",
        "X": "Χ",
        "O": "Ο",
        "P": "Π",
        "R": "Ρ",
        "S": "Σ",
        "T": "Τ",
        "Y": "Υ",
        "F": "Φ",
        "C": "Ψ",
        "V": "Ω"
    };

    const main = () =>
        either(
            () => ""
        )(
            cs => cs.join("")
        )(
            bindLR(
                clipTextLR()
            )(
                clipText => {
                    const
                        dict = containsGreek(clipText) ? (
                            gkEn
                        ) : enGk;

                    return Right(
                        [...clipText].map(
                            c => dict[c] || c
                        )
                    );
                }
            )
        );

    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = lr =>
        // Bind operator for the Either option type.
        // If lr has a Left value then lr unchanged,
        // otherwise the function mf applied to the
        // Right value in lr.
        mf => "Left" in lr ? (
            lr
        ) : mf(lr.Right);

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        // Either a message, (if no clip text is found),
        // or the string contents of the clipboard.
        const
            v = ObjC.unwrap(
                $.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString)
            );

        return Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left("No utf8-plain-text found in clipboard.");
    };


    // containsGreek :: String -> Bool
    const containsGreek = s =>
        (/[\u0370-\u1FFF]/u).test(s);


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => "Left" in e ? (
            fl(e.Left)
        ) : fr(e.Right);

    // MAIN ---
    return main();
})();

Just updated one mapping issue in the code above (macro and listing)

on this system at least:

  • whereas medial sigma σ is mapped to Anglo 's'
  • final sigma ς is mapped to Anglo 'w'

(not sure whether that is what you are seeing in the keyboard layouts you are using)

2 Likes

Works great my friend! Thank you so much! Have a nice weekend!

1 Like