I own a clone produced in Ukrainian Soviet Republic - "ОРЕЛЬ БК-08". It supports Cyrillic and Latin fonts.
The main idea is similar to ANSI.SYS approach for DOS. There is a special control symbol that is not displayed, but which will switch the current font when being sent to print.
The БК-08 had a РУС key on its keyboard that "switch" the language by inserting the required service symbols as you type the text.
The keyboard is actually very decent, almost matching the PC keyboard layout. The new keys that was not present in the original Speccy - like the arrows navigation, edit/graph/tab/backspace - did not introduce new keycodes, but instead were mapped in the hardware to the combination of Caps/Symbol Shift and the key corresponding to original ZX Spectrum rules.
So in the memory your string would be "Hello <РУС>Кириллица<LAТ> and Latin"
, with <РУС>/<LAТ> being a single byte control symbol. When printing the text on screen the BASIC would jump between fonts when encountering control symbols. And if you press that РУС button multiple times it would insert new control symbol each time, so editing the line with Backspace would switch the current language (indicated by the letter in blinking cursor) again and again as you delete the last control symbols. Even navigating through the line with left/right arrow keys would visually "stall" on the sequence of such symbols and indicate language changes with cursor, moving past the character position only after you've stepped past all the control symbols.
Both Latin an Cyrillic fonts are present in the ROM simultaneously, there was enough unused space left in the original 48K ROM. Since there is no true text mode in ZX Spectrum, there's no problem having symbols from both fonts displayed simultaneously on the screen - that's only a result of drawing copies of symbols, and there's no way of knowing who and how did print those copies. There are also no separate symbol codes for different languages. Strictly speaking there are no languages, just fonts and control symbols to switch between them when outputting symbols strictly in serial manner. The Cyrillic texts in strings were represented with Latin symbols with alternative font when being printed. Come to think of it, the very same way ZX Spectrum represents the texts in inverse color.
The PRINT function always starts with Latin font. The code to switch to Cyrillic font is 0x1E and the code to switch to Latin is 0x1F.
There is no global 'language' state, these markers belong to the string data and only interpreted by PRINT during symbols rendering. Sort of markup language tags. So when looking at the symbol alone out of the string data context there is no way to tell if it's Latin or Cyrillic symbol.
When you enter the new BASIC line the new line editor always starts in Latin mode. As you switch the language, the editor inserts the symbol to switch to the other font, apparently it tracks the selection in the temporary editor state, and knows which language is active for the letter at current cursor position. The whole line is redrawn after any change in the string, as a consequence of this redraw the rest of the line instantly gets reinterpreted as different language up until the next encountered marker.
Since the BASIC keywords in program lines are represented with tokens and not with plain texts, the language switch never alters the keywords representation, the keyword tokens are always rendered as Latin texts.
The symbols encoding for Cyrillic looks very much like KOI-7 N1 (probably it is) - i.e. the symbols roughly correspond to Latin letters phonetically, and the lower/upper cases are swapped.
Comparing strings АБВГ
то ABWG
would not return equal because of the difference in cases, and because of the markup markers present in the АБВГ
string. Cyrillic АБВГ
string would be at least 5 bytes long (starting with non-printable 0x1E).
If during INPUT statement execution you switched language multiple times before finally settling on the language you will be typing, or if you switched the language at the different character (e.g. before or after the whitespace) then such string would not be equal to string constants in BASIC program that has markers at some exact positions. You could easily end up with the Latin text that looks entirely Latin, but inside it would have tons of redundant or cancelling each other markers.
Comparing Cyrillic texts was not a good idea without transforming inputs to some canonical representation, but probably nobody bothered to do so.
As example here's the BASIC program printing mixed languages text:
And this is its output:
This did come at cost though, since this broke the compatibility with games that were using IM2 interrupt mode and pointing interrupt vector to the ROM area which was expected to be filled with 0xFF. Since it wasn't 0xFF anymore (there is a Cyrillic font now), such games were crashing. This could be "fixed" by loading original 48K ROM image file into the shadow ram (writing to ROM area would transparently write to shadow ram) and switching the banks between ROM chips and shadow ram by writing flags to a clone-specific I/O port.