Twitter's 1.1/statuses/update.json URL expects data to be encoded in application/x-www-form-urlencoded format, so you need to set the TRESTClient.ContentType property to ctAPPLICATION_X_WWW_FORM_URLENCODED (it is set to ctNone by default).
As for UTF-8, TRESTClient uses Indy internally, and Indy supports encoding outbound data using user-specified charsets, but it does not appear that Embarcadero added that feature to its TRESTClient interface (it does handle charsets in responses, though). I do not know why Embarcadero would omit such an important feature. It is not enough to just encode the string data as UTF-8 (which you are not doing correctly, BTW), but you also have to tell Twitter that the data has been UTF-8 encoded (via the charset attribute of the Content-Type REST header), and TRESTClient does not allow you to do that, as far as I can see. I don't know if TRESTClient sends REST requests with a default charset specified, but looking at its source, I don't think it does, but I have not tried it.
At the very least, you need to fix your EncodeAsUTF8() function. It does not produce a UnicodeString that holds UTF-8 encoded octets, like you think it does. It produces a UTF-8 encoded AnsiString and then converts that to a UTF-16 encoded UniodeString using the RTL's default Ansi codepage, so you are invoking a data conversion that loses the UTF-8 data. Try this instead:
function TTwitterApi.EncodeAsUTF8(UnicodeStr: string): string;
var
UTF8Str: UTF8String;
I: Integer;
begin
UTF8Str := UTF8String(UnicodeStr);
SetLength(Result, Length(UTF8Str));
for I := 1 to Length(UTF8Str) do
Result[I] := Char(Ord(UTF8Str[I]));
end;
That should allow TRESTClient to url-encode the correct UTF-8 data in its POST data, at least. But you still have to deal with the issue of the missing charset attribute in the Content-Type request header (unless Twitter defaults to UTF-8 when no charset is specified).
Now, with all of that said, if you find that working around the TRESTClient problems does not work out for you, then I would suggest switching to Indy's TIdHTTP component instead (which has a more accurate application/x-www-form-urlencoded implementation than TRESTClient is using), eg:
procedure TTwitterApi.Send(Tweet: string);
var
Params: TStringList;
begin
Reset;
Params := TStringList.Create;
try
FParams.Add('status=' + Tweet);
FIdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
FIdHTTP.Request.Charset := 'utf-8';
FIdHTTP.Post('https://api.twitter.com/1.1/statuses/update.json', Params, IndyTextEncoding_UTF8);
finally
Params.Free;
end;
end;