StringReplace adds "~~" to words with Tooltip

This is because the elements of wordsWithTooltips aren't strings. If you convert to strings, there's no issue.

StringReplace[txt, #1 -> ToString[#2, TraditionalForm] & @@@ tls]

gif


I prefer avoiding TraditionalForm strings, e.g.,

ToString[Tooltip[x/y,stuff],TraditionalForm]//FullForm
(* "\!\(\*FormBox[TagBox[TooltipBox[FractionBox[\"x\", \"y\"], \"stuff\"], 
Function[Annotation[Slot[1], stuff, \"Tooltip\"]]], TraditionalForm]\)" *)

Instead, I would use StringForm. The simplest version would be:

indices = {"`1`", "`2`", "`3`"};
StringForm[
    StringReplace[txt, Thread[Rule[words, indices]]],
    Sequence @@ Thread[Tooltip[words, tooltips]]
]
(* StringForm["Good, `2`, `3`. Never let it rest. 'Til your `1` is `2` and your 
`2` is `3`.", Tooltip["good", "good2"], Tooltip["better", "better2"],
Tooltip["best", "best2"]] *)

However, you wanted to use IgnoreCase. The Tooltip will have to be different for different cases, and a priori, you don't know how many versions with different cases there will be. So, the indices will have to be generated dynamically as the string is processed. To do this, I introduce a helper function:

toIndex[word_] := toIndex[word] = CompoundExpression[
    Sow[ToLowerCase[word] /. Thread[Rule[words, tooltips]], word],
    "`"<>ToString[i++]<>"`"
]

Then, I use this helper function to convert words into their indexed form:

Internal`InheritedBlock[{i=1, toIndex},
    Apply[
        StringForm[#1, Sequence@@#2]&,
        Reap[
            StringReplace[txt, x:Alternatives@@words :> toIndex[x], IgnoreCase->True],
            _String,
            Tooltip[#1, #2[[1]]]&
        ]
    ]
]
(* StringForm["`1`, `2`, `3`. Never let it rest. 'Til your `4` is `2` and your
`2` is `3`.", Tooltip["Good", "good2"], Tooltip["better", "better2"],
Tooltip["best", "best2"], Tooltip["good", "good2"]] *)

This question is closely related to:

  • How to 'merge' a list like FromDigits, but with a mixture of numbers and symbols?
  • Using subscript in a string

As Chip Hurst already explained your Tooltip expressions need to be converted to strings if they are to be appended as strings. You could also display the expression as a Row instead:

Row@*List @@ StringReplace[txt, Rule @@@ tls]

If that expression needs to be converted to a string it can be done en masse:

ToString[%, StandardForm];
% // Head
String