You're going to get uninitialized string offset notices in any language with that function due to the way you're iterating over the string bytes + colors. Better to access the colors via an InfiniteIterator which will just loop around and around.
Your specific problem with Vietnamese is that some of those characters are composed of multiple bytes. Functions like strlen() and accessing offsets via array brackets like $text[$i] are not multi-byte safe - they work on individual bytes rather than characters.
While it might be tempting to just use mb_strlen() in place of strlen() to get the number of characters rather than the number of bytes, and mb_substr() rather than $text[$i] to get a character rather than a byte, you'll still end up breaking up graphemes like è (which is here encoded as e followed by a combining grave accent.) A solution is to break up the string into an array with a regular expression.
Example:
function rainbow($text)
{
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
$return = '';
$colors = new InfiniteIterator(
new ArrayIterator(
['ff00ff', 'ff0099', 'ff0033', 'ff3300',
'ff9900', 'ffff00', '99ff00', '33ff00',
'00ff33', '00ff99', '00ffff', '0099ff',
'0033ff', '3300ff', '9900ff']
)
);
$colors->rewind();
// Match any codepoint along with any combining marks.
preg_match_all('/.\pM*+/su', $text, $matches);
foreach ($matches[0] as $char)
{
if (preg_match('/^\pZ$/u', $char)) {
// No need to color whitespace or invisible separators.
$return .= $char;
} else {
$return .= "<span style='color:#{$colors->current()};'>$char</span>";
$colors->next();
}
}
return $return;
}
echo rainbow('tôi yêu em evè foo baz');
Output:
<span style='color:#ff00ff;'>t</span><span style='color:#ff0099;'>ô</span><span style='color:#ff0033;'>i</span> <span style='color:#ff3300;'>y</span><span style='color:#ff9900;'>ê</span><span style='color:#ffff00;'>u</span> <span style='color:#99ff00;'>e</span><span style='color:#33ff00;'>m</span> <span style='color:#00ff33;'>e</span><span style='color:#00ff99;'>v</span><span style='color:#00ffff;'>è</span> <span style='color:#0099ff;'>f</span><span style='color:#0033ff;'>o</span><span style='color:#3300ff;'>o</span> <span style='color:#9900ff;'>b</span><span style='color:#ff00ff;'>a</span><span style='color:#ff0099;'>z</span>
</div>