Yo.
I need to scan a directory and its sub-folders, I used FindFirst and FindNext procedures, but the TSearchRec's Name property is a string, thus unicode folder names (hebrew, arabic etc) are '?????' in the Name property.
I tried using TntComponent, with WideFindFirst, WideFindNext and TSearchRecW.
But I still get ?????? for folder names.
Flname:=WideExtractFileName(FileSpec);
validres := WideFindFirst(FileSpec+'\*', faDirectory, SearchRec);
AssignFile(LogFile, ResultFilePath);
while validres=0 do begin
if (SearchRec.Attr and faDirectory>0) and (SearchRec.Name[1]<>'.') then begin
{invalid entry Findnext returns}
Append(LogFile);
WriteLn(LogFile, FileSpec+'\'+LowerCase(SearchRec.Name));
CloseFile(LogFile);
DirScan(FileSpec+'\'+SearchRec.Name, ResultFilePath)
end;
validres:=WideFindNext(SearchRec);
end;
WideFindClose(SearchRec);
Delphi does support unicode in the compiler by using WideString.
But you'll face the following problems:
Delphi < 2009 does not support unicode in their VCL.
A lot of API mapping is done on the ANSI (OpenFileA for instance) variants of the API.
The delphi compiler will convert the WideStrings to a string a lot, so be very explicit about them.
It will work if you use the raw unicode windows api's.
So FindFirst uses the api FindFirstFile which delphi maps to the FindFirstFileA variant, and you'll need to directly call FindFirstW.
So you'll have 2 options.
Upgrade to Delphi 2009 and have a lot of unicode mapping done for you
Write your own unicode mapping functions
For the text file writing you might be able to use the GpTextFile or GpTextSteam by Primoz Gabrijelcic (aka gabr), they have unicode support.
Her is an example of opening a file with a unicode filename:
function OpenLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle; overload;
begin
if CompareMem(#(WideCharToString(PWideChar(ALongFileName))[1]), #('\\'[1]), 2) then
{ Allready an UNC path }
Result := CreateFileW(PWideChar(ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
else
Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
end;
function CreateLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle; overload;
begin
if CompareMem(#(WideCharToString(PWideChar(ALongFileName))[1]), #('\\'[1]), 2) then
{ Allready an UNC path }
Result := CreateFileW(PWideChar(ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
else
Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
end;
I've used these functions because the ANSI api's have a path limit of 254 chars, the unicode have a limit of 2^16 chars if I'm not mistaken.
After you've got the handle to the file you can just call the regular ReadFile delphi api mapping, to read data from your file.
Delphi versions prior to 2009 have very limited unicode support. So if you really want unicode, then I strongly advise you to upgrade to 2009. In 2009 the default string is unicode.
You say that you still got garbage characters with the wide versions. Have you tried to inspect the values with the debugger? The vcl of pre 2009 delphi can not show unicode chars.
Some update.. If I use UTF8Encode on the SearchRec.Name I get the unicode string!
Next problem is TFileStream I use. I couldn't find a WideString version for it(for filenames).
You could use CreateFileW to open the file and get a handle then use THandleStream instead of TFileStream to read the file.
Tnt Unicode (TMS Unicode now) has a TTntFileStream which can open files based on widestring.
And as Gamecat stated, if you want to do unicode you should really upgrade to Delphi 2009. (awesome release in general)
Related
This line:
TFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
drops an exception if the filename contain something like ñ
You are, ultimately calling CreateFileA, the ANSI API, and the characters you use have no ANSI encoding. The only way to get beyond this is to open the file with CreateFileW, the Unicode API.
You might not realise that you call CreateFileA, but that's how the Delphi 7 file stream is implemented.
One easy way to solve your problems is to upgrade to the latest Delphi which has good support for the native Windows Unicode API.
If you are stuck with ANSI Delphi then you still need to call CreateFileW. You can do this to create a file handle. You'll need to pass a UTF-16 string to that API. Use WideString to store it. You'll also need to get the filename from the user in UTF-16 form. Which means a call to GetOpenFileNameW or IFileDialog. Create a stream by passing the file handle to THandleStream.
To make all this possible you would use the TNT Unicode libraries. They work well but will impose a big port on you.
Frankly, the right way forward is to use modern tools that support Unicode.
You can use the TntUnicode units to have UTF8 support under Delphi 7.
Add TntClasses to your Uses and make the call like this:
TTntFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
Make sure that fileName is widestring.
Here you can get a copy of TntUnicode:
https://github.com/rofl0r/TntUnicode
UTF16 can be thought of as a codepage, just like all of the possible ANSI codepages.
As Remy mentions in his comment, assuming your ANSI codepage supports the required characters in your Unicode string you simply have to convert that Unicode version of that string to the equivalent ANSI codepage version.
The Delphi compiler can take care of a simple conversion for you automatically, which you use simply by casting a WIDEString (UTF16) to an (ANSI)String:
const
WIDE_FILENAME : WIDEString = 'fuññy.txt';
var
sFilename: String;
strm: TFileStream;
begin
sFilename := String(WIDE_FILENAME);
strm := TFileStream.Create(sFilename, fmOpenRead);
// etc
end;
This works perfectly well even on (e.g.) Delphi 7. The only caveat is that the codepage involved (the system default) must support the extended characters in the Unicode string.
NOTE: The above code uses the String type rather than ANSIString explicitly. On Delphi versions where String is ANSIString, this has the required effect but also is portable to versions where String is UnicodeString (should you upgrade your version later).
If you use ANSIString explicitly in this case, the result will be a double conversion if/when you upgrade:
// Unicode compiler using ANSIString type....
var
sFilename: ANSIString;
begin
sFilename := ANSIString(WIDE_FILENAME); // Codepage conversion from UTF16 to ANSI
strm := TFileStream.Create(sFilename, fmOpenRead); // Will implicitly convert *back* from ANSI to WIDE
versus
// Unicode compiler using String type....
var
sFilename: String;
begin
sFilename := String(WIDE_FILENAME); // String type conversion from WideString to UnicodeString
strm := TFileStream.Create(sFilename, fmOpenRead); // No further conversion necessary
Best solution is to go Unicode, but if that is not an option, you can still solve the problem.
In Windows you can set what codepage to use for non-Unicode programs. Just change it to support the correct language (Spanish?). Then the code should work.
Windows 7: Control Panel > Region and Language > Administrative > Language for non-Unicode programs
Windows XP: Control Panel > Regional and Language > Advanced > Language for non-Unicode programs
This question already has answers here:
Handling a Unicode String in Delphi Versions <= 2007
(5 answers)
Closed 5 years ago.
I'm trying to do exactly what the title say, insert an emoji into a string in Delphi 2007, just like the example below :
procedure TForm1.Button1Click(Sender: TObject);
var s : string;
begin
s := 'This is my original string (y)';
s := ansireplacestr(s,'(y)','👍');
showmessage(s);
end;
I can even paste the emoji into IDE's code, but in runtime showmessage results in this :
This is my original string ????
Is there a way to achieve this task in Delphi 2007 ? Due to several reasons i can't upgrade Delphi right now.
Someone said my question is solved on this topic :
Handling a Unicode String in Delphi Versions <= 2007
But this topic just says to use third-party components, without telling exactly how to do it.
EDIT : After suggested, i tried to use the functions pos, delete and insert and a widestring var :
function addEmoji(mystring : widestring) : widestring;
var r, aux : widestring;
p : integer;
begin
r := mystring;
while pos('(y)',r) > 0 do
begin
aux := r;
p := pos('(y)',aux);
Insert('👍',aux,p);
delete(aux,pos('(y)',aux),3);
r := aux;
end;
result := r;
end;
But the result is the '(y)' replaced by '????'.
In Delphi 2007, the default string type is AnsiString. Emojis require Unicode handling, as they use high Unicode codepoints that simply do not fit/exist in most commonly used Ansi encodings. So you need to use a Unicode UTF encoding instead (UTF-7, -8, -16, or -32).
You can use AnsiString for UTF-71, or UTF8String2 for UTF-8, or WideString for UTF-16, or UCS4String3 for UTF-32.
1: UTF-7 is a 7-bit ASCII compatible encoding.
2: UTF8String does exist in Delphi 2007 (it was introduced in Delphi 6), but it is not a true UTF-8 string type, it is just an alias for AnsiString with the expectation that it always holds UTF-8 encoded data. You have to use UTF8Encode() and UTF8Decode() to ensure proper conversions to other encodings via UTF-16. UTF8String did not become a true UTF-8 string type until Delphi 2009 (UTF8Encode() and UTF8Decode() were also deprecated).
3: UCS4String also exists since Delphi 6, but it is not a true string type at all (even in modern Delphi versions). It is just an alias for array of UCS4Char.
The RTL doesn't have any native support for UTF-7 (but it is not hard to implement manually), and very little support for UTF-32 (only to facilitate conversions between UTF-16 <-> UTF-32), so you should stick with UTF-8 or UTF-16 in your code.
You are going to lose Emoji data if you convert UTF data to Ansi, such as if you pass a WideString to ShowMessage(). You can pass a WideString to the Win32 API MessageBoxW() function instead, and you won't have any data loss, however the Emoji may or may not appear correctly depending on the font used by the dialog (but it won't appear as ??, at least).
However, the native RTL in Delphi 2007 simply does not support what you are attempting, at least not for UTF-16. You would have to find a 3rd party WideString-based function, or just write your own using the RTL's Pos(), Delete(), and Insert() intrinsic functions, which are overloaded for WideString data, eg:
function WideReplaceStr(const S, FromText, ToText: WideString): WideString;
var
I: Integer;
begin
Result := S;
repeat
I := Pos(FromText, Result);
if I = 0 then Break;
Delete(Result, I, Length(FromText));
Insert(ToText, Result, I);
until False;
end;
var
s : WideString;
begin
s := 'This is my original string (y)';
s := WideReplaceStr(s, '(y)', '👍');
MessageBoxW(0, PWideChar(s), '', MB_OK);
end;
However, using UTF-8, you can accomplish the same thing using the native RTL, but you still can't use ShowMessage() (well, you could, but it won't show non-ASCII characters correctly):
var
s : UTF8String;
begin
s := UTF8Encode('This is my original string (y)');
s := AnsiReplaceStr(s, '(y)', UTF8Encode('👍'));
MessageBoxW(0, PWideChar(UTF8Decode(s)), '', MB_OK);
end;
Either way, make sure your code editor is set to save the .pas file in UTF-8, otherwise you can't use the literal '👍', you would have to use something more like this instead:
var
Emoji: WideString;
SetLength(Emoji, 2);
Emoji[1] := WideChar($D83D);
Emoji[2] := WideChar($DC4D);
Then you can do this:
var s: WideString;
...
s := WideReplaceStr(s, '(y)', Emoji);
Or:
var s: UTF8String;
...
s := AnsiReplaceStr(s, '(y)', UTF8Encode(Emoji));
This line:
TFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
drops an exception if the filename contain something like ñ
You are, ultimately calling CreateFileA, the ANSI API, and the characters you use have no ANSI encoding. The only way to get beyond this is to open the file with CreateFileW, the Unicode API.
You might not realise that you call CreateFileA, but that's how the Delphi 7 file stream is implemented.
One easy way to solve your problems is to upgrade to the latest Delphi which has good support for the native Windows Unicode API.
If you are stuck with ANSI Delphi then you still need to call CreateFileW. You can do this to create a file handle. You'll need to pass a UTF-16 string to that API. Use WideString to store it. You'll also need to get the filename from the user in UTF-16 form. Which means a call to GetOpenFileNameW or IFileDialog. Create a stream by passing the file handle to THandleStream.
To make all this possible you would use the TNT Unicode libraries. They work well but will impose a big port on you.
Frankly, the right way forward is to use modern tools that support Unicode.
You can use the TntUnicode units to have UTF8 support under Delphi 7.
Add TntClasses to your Uses and make the call like this:
TTntFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
Make sure that fileName is widestring.
Here you can get a copy of TntUnicode:
https://github.com/rofl0r/TntUnicode
UTF16 can be thought of as a codepage, just like all of the possible ANSI codepages.
As Remy mentions in his comment, assuming your ANSI codepage supports the required characters in your Unicode string you simply have to convert that Unicode version of that string to the equivalent ANSI codepage version.
The Delphi compiler can take care of a simple conversion for you automatically, which you use simply by casting a WIDEString (UTF16) to an (ANSI)String:
const
WIDE_FILENAME : WIDEString = 'fuññy.txt';
var
sFilename: String;
strm: TFileStream;
begin
sFilename := String(WIDE_FILENAME);
strm := TFileStream.Create(sFilename, fmOpenRead);
// etc
end;
This works perfectly well even on (e.g.) Delphi 7. The only caveat is that the codepage involved (the system default) must support the extended characters in the Unicode string.
NOTE: The above code uses the String type rather than ANSIString explicitly. On Delphi versions where String is ANSIString, this has the required effect but also is portable to versions where String is UnicodeString (should you upgrade your version later).
If you use ANSIString explicitly in this case, the result will be a double conversion if/when you upgrade:
// Unicode compiler using ANSIString type....
var
sFilename: ANSIString;
begin
sFilename := ANSIString(WIDE_FILENAME); // Codepage conversion from UTF16 to ANSI
strm := TFileStream.Create(sFilename, fmOpenRead); // Will implicitly convert *back* from ANSI to WIDE
versus
// Unicode compiler using String type....
var
sFilename: String;
begin
sFilename := String(WIDE_FILENAME); // String type conversion from WideString to UnicodeString
strm := TFileStream.Create(sFilename, fmOpenRead); // No further conversion necessary
Best solution is to go Unicode, but if that is not an option, you can still solve the problem.
In Windows you can set what codepage to use for non-Unicode programs. Just change it to support the correct language (Spanish?). Then the code should work.
Windows 7: Control Panel > Region and Language > Administrative > Language for non-Unicode programs
Windows XP: Control Panel > Regional and Language > Advanced > Language for non-Unicode programs
I am editing a open source project called Opencv for Delphi which compiles fine with Delphi 6 , Delphi 2009 and Delphi xe2.
I just removed incompatible (eg :Application.MainFormOnTaskbar := True;) for the Delphi 6 compilation.
On the run time d6 app works fine without errors, but the rest are compiling well but with run time error when calling cvopencv.dll.
The original project is a Spanish project compiled with Delphi 2007 which works fine.
In the d6 exe i can't read any thing except ??????? but others showing in spanish which means it is a UNICODE problem, when calling cvopencv.dllthe error happens.
What can i do to make this project work with Delphi 2009 or higher (still d2009 compiling well)
========================= After David Heffemans Answer =====================
procedure TForm1.Button2Click(Sender: TObject);
var
file1 : PAnsiChar; // "haarcascade_frontalface_alt.xml";
file2 : PAnsiChar; //"haarcascade_eye.xml";
file3 : PAnsiChar; //"haarcascade_upperbody.xml";
SourceFileName : AnsiString;
StorageType : Integer;
ImSize : CvSize;
begin
memo1.Lines.Clear;
GetMem(Storage, SizeOf(CvMemStorage));
SourceFileName:=Edit1.Text;
StorageType:=0;
storage:=nil;
storage :=cvCreateMemStorage(storageType);
file1 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_frontalface_alt.xml');
file2 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_eye.xml');
file3 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_upperbody.xml');
cascade_f := cvLoad(file1, nil, nil, nil);
cascade_e := cvLoad(file2, nil, nil, nil);
cascade_ub := cvLoad(file3, nil, nil, nil);
if cascade_f=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä Face');
if cascade_e=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä Eye');
if cascade_ub=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä UpperBody');
img := cvLoadImage(PAnsiChar(AnsiString(SourceFileName)), 1);
cvNamedWindow((PAnsiChar(AnsiString(Edit1.Text))), 1);
detectEyes(img);
cvShowImage(PAnsiChar(AnsiString(SourceFileName)), img);
end;
end.
The OpenCV library you refer to says this at the top of the header:
Borland Delphi 4,5,6,7 API for Intel Open Source Computer Vision Library
This code presents a Delphi wrapper around the OpenCV DLLs.
For these older Delphi versions, Char, PChar and string map to AnsiChar, PAnsiChar and AnsiString respectively. This matches the OpenCV DLLs which are pure ANSI. There are no Unicode APIs in OpenCV (at least so far as I can discern).
If you wish to use this unit from Delphi 2009 and up you need to recognise that in Delphi 2009, Unicode support was added. This changed the mapping of Char, PChar and string map to WideChar, PWideChar and UnicodeString respectively. This is the cause of the incompatibility you have encountered because the DLL behind this unit is still processing single byte ANSI text.
Probably the most sensible approach with this unit is to change it to use AnsiChar, PAnsiChar and AnsiString. You can do this with a global search and replace in the unit.
This will make the OpenCV.pas unit match the DLLs again. You now need to be careful how you interface with this OpenCV.pas unit. The functions that receive PAnsiChar parameters need special treatment.
For example consider the following example:
Function cvLoadImage( const filename : PChar;
iscolor : integer=1) : PIplImage; cdecl;
This is the original declaration. After your search and replace it will read like this:
Function cvLoadImage( const filename : PAnsiChar;
iscolor : integer=1) : PIplImage; cdecl;
When you come to call it you need to ensure you pass a proper PAnsiChar. Like this:
var
FileName: string;
...
image := cvLoadImage(PAnsiChar(AnsiString(FileName)));
You must convert from string (which maps to UnicodeString) to AnsiString and only then can you cast to PAnsiChar. Do not write PAnsiChar(FileName). This will not convert the Unicode string to ANSI and will fail.
Fortunately for you the OpenCV library has a relatively small number of APIs that receive text.
See the project - Delphi-OpenCV on github (OpenCV version - 2.4.6, environment - Delphi XE2-XE4). Translation of OpenCV library header files in Delphi: https://github.com/Laex/Delphi-OpenCV
In the past I used INI-Files to store unicode text, but now I need to store unicode text in the executable. How can I achieve this?
I want to store these letters:
āčēūīšķļņž
If you want to save the Unicode INI files then you might try the following code. The files are saved in UTF8 encoding.
Also you might take a look at this Unicode library where you can find a lot of helper functions.
uses IniFiles;
function WideStringToUTF8(const Value: WideString): AnsiString;
var
BufferLen: Integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, nil, 0, nil, nil);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, PAnsiChar(Result), BufferLen - 1, nil, nil);
end;
end;
function UTF8ToWideString(const Value: AnsiString): WideString;
var
BufferLen: integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, nil, 0);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, PWideChar(Result), BufferLen - 1);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
IniFile: TIniFile;
const
UnicodeValue = WideString(#$0101#$010D#$0113#$016B#$012B#$0161);
begin
IniFile := TIniFile.Create('C:\test.ini');
try
IniFile.WriteString('Section', 'Key', WideStringToUTF8(UnicodeValue));
IniFile.UpdateFile;
finally
IniFile.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
IniFile: TIniFile;
UnicodeValue: WideString;
begin
IniFile := TIniFile.Create('C:\test.ini');
try
UnicodeValue := UTF8ToWideString(IniFile.ReadString('Section', 'Key', 'Default'));
MessageBoxW(Handle, PWideChar(UnicodeValue), 'Caption', 0);
finally
IniFile.Free;
end;
end;
with Delphi 2007 on 64-bit Windows 7 Enterprise SP 1
If you definitely need to use Delphi 7 there are some variants:
Store strings in resources linked to executable file.
Store strings in big memo or same thing, located on global data module or any other visual or non-visual component and access it by index. It's possible because strings in Delphi resources stored in XML-encoded form. E.g. your symbols example āčēūīšķļņž will be stored as āčēūīšķļņž
Store XML-encoded or Base64-encoded strings in string constants inside your code.
For string conversion you can use EncdDecd.pas , xdom.pas or some functions of System.pas like UTF8Encode/UTF8Decode.
To display and edit Unicode strings in Delphi forms you can use special set of Unicode controls like TNT Unicode Controls or subclass original Delphi controls and do some other workarounds by yourself, like described in this excerpt from comments in TntControls.pas (part of TNT Unicode Controls):
Windows NT provides support for native Unicode windows. To add
Unicode support to a
TWinControl descendant, override CreateWindowHandle() and call
CreateUnicodeHandle().
One major reason this works is because the VCL only uses the ANSI
version of
SendMessage() -- SendMessageA(). If you call SendMessageA() on a
UNICODE
window, Windows deals with the ANSI/UNICODE conversion
automatically. So
for example, if the VCL sends WM_SETTEXT to a window using
SendMessageA,
Windows actually expects a PAnsiChar even if the target window
is a UNICODE
window. So caling SendMessageA with PChars causes no problems.
A problem in the VCL has to do with the TControl.Perform() method.
Perform()
calls the window procedure directly and assumes an ANSI window.
This is a
problem if, for example, the VCL calls Perform(WM_SETTEXT, ...)
passing in a
PAnsiChar which eventually gets passed downto DefWindowProcW()
which expects a PWideChar.
This is the reason for SubClassUnicodeControl(). This procedure
will subclass the
Windows WndProc, and the TWinControl.WindowProc pointer. It will
determine if the
message came from Windows or if the WindowProc was called
directly. It will then
call SendMessageA() for Windows to perform proper conversion on
certain text messages.
Another problem has to do with TWinControl.DoKeyPress(). It is
called from the WM_CHAR
message. It casts the WideChar to an AnsiChar, and sends the
resulting character to
DefWindowProc. In order to avoid this, the DefWindowProc is
subclassed as well. WindowProc
will make a WM_CHAR message safe for ANSI handling code by
converting the char code to
#FF before passing it on. It stores the original WideChar in the
.Unused field of TWMChar.
The code #FF is converted back to the WideChar before passing onto
DefWindowProc.
Do
const MyString = WideString('Teksts latvie'#$0161'u valod'#$0101);
Simple, the idea is to find a non-visual component, which can store text and store your text there. Prefer that such component can also provide you an editor to edit the text in design time.
There is a component call FormResource which can do this. I use TUniScript. I believe there are other similar components. However, I did not find a usable component from the standard library.
The approach Widestring(#$65E5#$672C) does not work, because Delphi 7 just doesn't expect more than one byte for the #, so the outcome is by far not what you expect when going above 255 or $FF.
Another approach WideChar($65E5)+ WideChar($672C) can be used to store single Unicode codepoints in your source code when knowing that you need to have a Widestring at the start of the assignment (which can also be an empty literal) so the compiler understands which datatype you want:
const
// Compiler error "Imcompatible types"
WONT_COMPILE: WideChar($65E5)+ WideChar($672C);
// 日本
NIPPON: Widestring('')+ WideChar($65E5)+ WideChar($672C);
Looks cumbersome, but surely has your UTF-16 texts in Delphi 7.
Alternatively, store your constants in UTF-8, which is ASCII safe - that way you can use # easily. One advantage is, that it's a lot less cumbersome to write in your source code. One disadvantage is, that you can never use the constant directly, but have to convert it to UTF-16 first:
const
// UTF-8 of the two graphemes 日 and 本, needing 3 bytes each
NIPPON: #$E6#$97#$A5#$E6#$9C#$AC;
var
sUtf16: Widestring;
begin
// Internally these are 2 WORDs: $65E5 and $672C
sUtf16:= UTF8ToWideString( NIPPON );