g | x | w | all
Bytes Lang Time Link
338Python 3250526T110429ZRhaixer
09405AB1E250506T123141ZKevin Cr
178Ruby p250507T012729ZValue In
096Jelly250504T165826ZJonathan
173sed250504T084023ZExplorer
170JavaScript V8250503T214959ZArnauld
140Retina 0.8.2250504T090803ZNeil
232Google Sheets250503T212208Zdoubleun

Python 3, 338 bytes

Let me know if I messed up somewhere. Input is as a list of lists with two items, with the last item of the argument always being the string "me".

x=lambda n,f=1,t=" they came for the ",j="—and there was no one left to speak for me.",a=", and I did not speak out—\nBecause I was not ":(f"First{t+((n[0][0]+a+n[0][1]))}.\n\n"+x(n[1:],0)if['me']!=n and f else(f"Then{t+n[0][0]+a+n[0][1]}.\n\n"+(x(n[1:],0)))if['me']!=n and f<1else"Then"+t[:15]+"me"+j)if"me"!=n else"First"+t[:15]+n+j

Try it online!

05AB1E, 101 97 95 94 bytes

ε…€»‹é€‡ð«”‚½€Û”#ð«N_èrNIgαiD“
and€Ç€¥€¸€µ…¸€„¡´€‡ “ë`”ƒ«”ŠŠ“€€ ÿ,€ƒ Iƒ§€–¡´€Ä
ÿ I€¥€– “}s'.J,

Uses newlines instead of , without empty lines in between verses.

Try it online or verify all test cases.

Explanation:

ε             # Map over the (implicit) input-list:
              #  (implicitly push the current pair - or "me")
 …€»‹é€‡      #  Push dictionary string "they came for"
        ð«    #  Append a space: "they came for "
 ”‚½€Û”       #  Push titlecased dictionary string "Then First"
       #      #  Split it on spaces: ["Then","First"]
        ð«    #  Append a space to each: ["Then ","First "]
          N_  #  Check if the 0-based map-index equals 0
              #  (1 in the first iteration; 0 in every other iteration)
            è #  Use that to index into the pair
 r            #  Reverse the three values on the stack,
              #  so the current pair or "me" is at the top again
  NIgαi       #  If the absolute difference between the map-index `N` and the
              #  input-length equals 1 (aka it's the last iteration):
      D       #   Duplicate the "me"
       “\nand€Ç€¥€¸€µ…¸€„¡´€‡ “
              #   Push dictionary string "\nand there was no one left to speak for "
     ë        #  Else:
      `       #   Pop the pair and push both items separately
       ”ƒ«”   #   Push titlecased dictionary string "Because"
           ŠŠ #   Triple-swap twice
             “€€ ÿ,€ƒ Iƒ§€–¡´€Ä\nÿ I€¥€– “
              #   Push dictionary string "the ÿ, and I did not speak out\nÿ I was not ",
              #   where the two `ÿ` are automatically replaced with the top two strings
     }s       #  After the if-else, swap the top two strings
       '.    '#  Push "."
         J    #  Join the five or six strings on the stack together
          ,   #  Pop and output it with trailing newline

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why …€»‹é€‡ is "they came for"; ”‚½€Û” is "Then First"; “\nand€Ç€¥€¸€µ…¸€„¡´€‡ “ is "\nand there was no one left to speak for "; ”ƒ«” is "Because"; and “€€ ÿ,€ƒ Iƒ§€–¡´€Ä\nÿ I€¥€– “ is "the ÿ, and I did not speak out\nÿ I was not ".

Ruby -p, 178 bytes

Input is separated with commas and newlines.

gsub(/.+?((,)|$)/){$`[0]?"Because I was not #$&.
":"#{$.>1?:Then:'First'} they came for #$&#{$2?" and I did not speak out—
":"—and there was no one left to speak for #$&."}"}

Attempt This Online!

Explanation

The -p flag takes each line of input, processes it by running the code, and prints it.

gsub replaces every instance of the input that matches /.+?((,)|$)/, which basically gets as many characters as it can until it hits a comma or newline, then replaces it with the corresponding phrase. #$& inserts the match into each relevant string, while #{...} evaluates the inside and inserts that into the string.

$` is the part of the line before the match. So if it has any characters at all ($`[0]) we know we're replacing the second half and output accordingly.

$. is the number of lines that have been read, so if it's 1 we output "First", otherwise "Then". :Then is one byte shorter than 'Then', but :First doesn't save bytes because you need a space when it's at that end of the conditional.

$2 tests for the presence of the second matched group (the comma) to decide whether to output the first part of the usual stanzas or the last one.

Jelly, 96 bytes

“¡.ƙĿʠṚoḢ¶wṫBmqÆ⁷x§ƇƊK¤b⁼⁹ṾÄƥẸ+ṭṪɼṡWỤṠṙƒḢ@⁶©ŀ»)Yṣ”AżḣLFḣ-6Żṛ"“¡Ṛf»“¤ḍŒ?=Æ^ṙM©ḅḳḷGƥl)AƥƓ⁸7ɓṢ7ṭÆ6»

A full program that accepts an odd-length list of strings and prints the poem to stdout.

Try it online!

How?

“...»)Yṣ”AżḣLFḣ-6Żṛ"“¡Ṛf»“...» - Main Link: list of lists of characters, Parts
     )                         - for each {Part}:
“...»                          -   Compressed string:
                                     Then they came for the A, and I did not speak out
                                     Because I was not A.
      Y                        - join with newline characters
       ṣ”A                     - split at 'A' characters
          ż                    - zip with {Parts}
           ḣL                  - head to length of {Parts}
             F                 - flatten
              ḣ-6              - head to index -6 (remove "the me" from the right)
                 Ż             - prefix with a zero
                  ṛ"“¡Ṛf»      - use "First" as the first five elements
                               - implicit print
                         “...» - Compressed string:
                                   me
                                   and there was no one left to speak for me.
                               - implicit print

sed, 173 bytes

1,/^me$/s/\(.*\),\(.*\)/the \1, and I did not speak out\nBecause I was not \2./;s/^me/&\nand there was no one left to speak for &./;s/^/Then they came for /;1s/^Then/First/;

sed (-r), 169 bytes

1,/^me$/s/(.*),(.*)/the \1, and I did not speak out\nBecause I was not \2./;s/^me/&\nand there was no one left to speak for &./;s/^/Then they came for /;1s/^Then/First/;

Try it online!

This entry was submitted by the author of this challenge and serves as an example.

Nouns are specified as "plural form, comma (,), singular form, newline".

I chose to use a newline in place of an em dash just to make the output ASCII friendly.

JavaScript (V8), 170 bytes

Expects a list of pairs of strings, followed by a singleton for "me". Prints the verses without the em-dashes.

a=>{for([x,y]of a)a=print(a?"First":"Then","they came for",y?`the ${x}, and I did not speak out
Because I was not ${y}.`:"me and there was no one left to speak for me.")}

Try it online!

Commented

a => {                         // a[] = input array
  for([x, y] of a)             // for each pair of strings (x, y) in a[]:
    a =                        //   force a to undefined after the 1st iteration
    print(                     //   print:
      a ?                      //     if a is still defined:
        "First"                //       "First"
      :                        //     else:
        "Then",                //       "Then"
      "they came for",         //     followed by "they came for"
      y ?                      //     followed by, if y is defined:
        `the ${x}, [..] ${y}.` //       the 1st type of verse with x and y
      :                        //     else:
        "me [..] for me."      //       the 2nd type of verse (hard-coded)
    )                          //   end of print() / implicit end of for
}                              //

Retina 0.8.2, 140 bytes

.+$
$&-and there was no one left to speak for $&
.+,
the $& and I did not speak out-Because I was not 
.+
Then they came for $&.
^Then
First

Try it online! This is the bonus version that accepts any final entity at a cost of 4 bytes. Output uses -s instead of dashes and newlines.

Google Sheets, 232 bytes

=let(a,"First",b,"Then",c," they came for ",index(tocol({if(row(A:A)=1,a,b)&c&"the "&tocol(A:A,1)&" and I did not speak out
Because I was not "&tocol(B:B,1)&".";if(len(B1),b,a)&c&"me—and there was no one left to speak for me."},2)))

Put the plurals and the me in column A, and the singulars in column B. There's a newline after out.

screenshot

In the event the subject's name may vary, as in me, you, or Anne Frank, put it in cell C1, and use this formula (237 bytes):

=let(a,"First",b,"Then",c," they came for ",index(tocol({if(row(A:A)=1,a,b)&c&"the "&tocol(A:A,1)&" and I did not speak out
Because I was not "&tocol(B:B,1)&".";if(len(A1),b,a)&c&C1&"—and there was no one left to speak for "&C1&"."},2)))