Prusa-Firmware/Firmware/language.c

315 lines
11 KiB
C
Raw Normal View History

//language.c
#include "language.h"
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include "bootapp.h"
#include "Configuration.h"
2018-06-21 17:58:13 +00:00
#include "pins.h"
2021-03-05 07:51:23 +00:00
#ifdef XFLASH
#include "xflash.h"
#include "xflash_layout.h"
2021-03-05 07:51:23 +00:00
#endif //XFLASH
// Currently active language selection.
uint8_t lang_selected = 0;
#if (LANG_MODE == 0) //primary language only
2020-09-11 13:12:49 +00:00
uint8_t lang_select(_UNUSED uint8_t lang) { return 0; }
uint8_t lang_get_count() { return 1; }
2020-09-11 13:12:49 +00:00
uint16_t lang_get_code(_UNUSED uint8_t lang) { return LANG_CODE_EN; }
const char* lang_get_name_by_code(_UNUSED uint16_t code) { return _n("English"); }
void lang_reset(void) { }
uint8_t lang_is_selected(void) { return 1; }
#else //(LANG_MODE == 0) //secondary languages in progmem or xflash
//reserved xx kbytes for secondary language table
const char _SEC_LANG[LANG_SIZE_RESERVED] __attribute__((aligned(SPM_PAGESIZE))) PROGMEM_I2 = "_SEC_LANG";
//primary language signature
const uint32_t _PRI_LANG_SIGNATURE[1] __attribute__((section(".progmem0"))) = {0xffffffff};
//lang_table pointer
lang_table_t* lang_table = 0;
const char* lang_get_translation(const char* s)
{
if (lang_selected == 0) return s + 2; //primary language selected, return orig. str.
if (lang_table == 0) return s + 2; //sec. lang table not found, return orig. str.
uint16_t ui = pgm_read_word(((uint16_t*)s)); //read string id
New PO-based language translation support (#3471) * lang: Add a PO language extractor with FW metadata support Implement a straight-to-po language extractor which supports our custom language requirements: - _i/_I/ISTR for text string definitions - _T for catalog translations (with back-reference support) - //// EOL comments with: - MSG_ catalog entry name identifiers - c=X r=Y annotations for screen dimensioning checks - Crude support for commented lines All source locations are correctly referenced in the PO, with the metadata colleted in the comment for further processing. Several checks are implemented already during extraction: - Correct catalog name assignment (no duplicates) - Metadata checks for each entry Further checks will be implemented by directly checking the translated PO file. Requires "polib" and "regex" python modules. * lang: Adapt lang-check to work directly on PO/POT files * lang: Allow lang-extract to generate stable (pre-sorted) output directly * lang: Further extend lang-extract consistency/error checking - Do not parse inside preprocessor conditionals - Distinguish between references and definitions - Warn about missing references and definitions * lang: lang-extract: warn about incorrect PROGMEM assignments Check that ISTR is used along with PROGMEM_I1 in an attempt to spot useless translated catalogs. * lang: lang-extract: Improved handling of same-line translations Correctly reference metadata on same-line translations. * lang: lang-extract: Handle _O as a cat-ref https://github.com/prusa3d/Prusa-Firmware/pull/3434 * lang: lang-extract: Warn about unused catalog definitions * lang: lang-extract: Allow propagating translation comments via // The definition: code //// definition [// comment] will check [definition] as before, but blindly accumulate // comment. The comment is then re-appended back into the PO files for translators with the form: definition comment comment... * lang: Fix incorrect display definitions * lang: lang-extract: Check source encoding/charmap * lang: Translate the degree symbol * lang: Unbreak/cleanup DEBUG_SEC_LANG * lang: Improve meaning of comment * lang: Split charset conversions into an aux lib for future use * lang: Implement lang-map.py to extract the translation symbol map - Extracts the translatable symbol map for further use - Computes a stable "language signature" from the map itself - Optionally patches the binary update the symbols * lang: Check for translation recoding problems * lang: Implement a transliteration map to post-process translations TRANS_CHARS is now used to replace unavailable symbols to the source encoding, only while producing the language catalog. * lang: Handle/check character replacements in lang-check Filter the translation through TRANS_CHARS, so that the preview and length check are performed correctly for expanding replacements such as 'ß' to 'ss'. * lang: Implement lang-build.py to generate the final language catalog * Cleanup .gitignore * lang: Drop txt language files * lang: Remove outdated translation scripts and obsolete docs * lang: Update build scripts for new infrastructure * lang: [no] Integrate accents from po/new/no.po We now support accents natively * lang: Remove redundant directory po/new/ * lang: Fix encoding of LCD characters in PO files * lang: [hr] Fix wrapping in MSG_CRASH_DET_ONLY_IN_NORMAL * lang: Sort and reformat PO files for further massaging * lang: Switch to developer (dot) comments for PO metadata * lang: Allow the IGNORE annotation to skip extraction * lang: Fix missing/broken language metadata in sources * lang: Add update-pot.sh and regenerate po/Firmware.pot * lang: Add update-po.sh and refresh all PO files * lang: Add summary documentation about the new translation workflow * Add more ignored files * CI: Add new required dependencies to travis * lang: lang-build: Improve warning message "referenced" was really meaning that data is being duplicated. * lang: Respect the language order as defined in config.sh This correctly splits normal and community-made entries during language selection. * lang: More typos in the documentation * lang: Check for the maximum size of each language Each table needs to fit within LANG_SIZE_RESERVED * lang: Properly align _SEC_LANG to page boundaries ... instead of relying on _SEC_LANG_TABLE to calculate the offset * lang: Build support for dual-language hex files Detect the printer type by checking the current variant type. On printers with no xflash (MK2*), generate one hex file for each additional language file by patching the built-in secondary language table during the build process * lang: Mention lang-patchsec.py * lang: Use color() instead of tput for clarity * lang: Allow disabling terminal colors with NO_COLOR/TERM=dumb * lang: Consistent use of redirection in config.sh * lang: Stricter variant-type check for xflash support * lang: Output size stats when building double-language hex files * lang: Respect NO_COLOR in lang-check.py * lang: Check for repeated/incorrect annotations Catch errors such as "c=1 c=2" * lang: Correct MSG_SLIGHT_SKEW/MSG_SEVERE_SKEW annotations * lang: [it] Improve MSG_*_SKEW translation * lang: Use INTLHEX instead of OUTHEX_P/S for configuration We already have OUTHEX which is the compiled firmware. Use INTLHEX for the final internationalized firmware, which is less confusing. Also, assume it being a prefix for all generated hex files, which reduces the number of variables set. * lang: Move lang_map to lib.io for further use * lang: lang-check: Accept a firmware map file to suppress unused string warnings * lang: Use the map file to reduce useless warnings during fw-build * lang: lang-check: Also suppress unused empty annotations * lang: Fix MSG_MOVE_CARRIAGE_TO_THE_TOP_Z annotation Refresh pot file * lang: lang-check: Do not warn about same-word translations by default Do not warn when one-word translations such as "No" result in "No" also in other languages, since this is common in latin languages. Allow to re-enable the warning with --warn-same * lang: lang-build: Handle same-source/translation efficiently * lang: [it] Explicitly add On/Off/Reset/Wizard to suppress warnings Instead of displaying a warning, supress the warning and explicitly translate each entry using english (which is the common/acceptable word in these cases). * lang: [it] Suppress more warnings * lang: lang-check: Add intermediate "suggest" warning category Warnings in the "suggest" category as shown as [S] as based on pure speculation from the checking tool, such as the translation being significantly shorter than the original. As a result, they can be suppressed with --no-suggest * lang: Return translation status from lang-check - 0 if the translation only contains suggestions - 1 if the translation contains warnings or errors Check for the exit status in fw-build.sh, but do nothing at the moment except printing a non-fatal error. * lang: Remove "trim_trailing_whitespace=false" for po files PO files got cleaned up/rewritten. We can now ensure they stay consistent. * lang: [sv] Re-integrate changes from 70c73cb * lang: [no] Reintegrate changes from @pkg2000
2022-06-16 13:03:30 +00:00
if (ui == 0xffff) return s + 2; //id not assigned, return orig. str.
ui = pgm_read_word(((uint16_t*)(((char*)lang_table + 16 + ui*2)))); //read relative offset
if (pgm_read_byte(((uint8_t*)((char*)lang_table + ui))) == 0) //read first character
return s + 2;//zero length string == not translated, return orig. str.
return (const char*)((char*)lang_table + ui); //return calculated pointer
}
uint8_t lang_select(uint8_t lang)
{
if (lang == LANG_ID_PRI) //primary language
{
lang_table = 0;
lang_selected = lang;
}
2021-03-05 07:51:23 +00:00
#ifdef XFLASH
if (lang_get_code(lang) == lang_get_code(LANG_ID_SEC)) lang = LANG_ID_SEC;
if (lang == LANG_ID_SEC) //current secondary language
{
if (pgm_read_dword(((uint32_t*)_SEC_LANG_TABLE)) == LANG_MAGIC) //magic valid
{
if (lang_check(_SEC_LANG_TABLE))
if (pgm_read_dword(((uint32_t*)(_SEC_LANG_TABLE + 12))) == pgm_read_dword(((uint32_t*)(_PRI_LANG_SIGNATURE)))) //signature valid
{
lang_table = (lang_table_t*)(_SEC_LANG_TABLE); // set table pointer
lang_selected = lang; // set language id
}
}
}
2021-03-05 07:51:23 +00:00
#else //XFLASH
if (lang == LANG_ID_SEC)
{
uint16_t table = _SEC_LANG_TABLE;
if (pgm_read_dword(((uint32_t*)table)) == LANG_MAGIC) //magic valid
{
if (lang_check(table))
if (pgm_read_dword(((uint32_t*)(table + 12))) == pgm_read_dword(((uint32_t*)(_PRI_LANG_SIGNATURE)))) //signature valid
{
lang_table = (lang_table_t*)table; // set table pointer
lang_selected = lang; // set language id
}
}
}
2021-03-05 07:51:23 +00:00
#endif //XFLASH
if (lang_selected == lang)
{
eeprom_update_byte((unsigned char*)EEPROM_LANG, lang_selected);
return 1;
}
2018-06-08 00:03:25 +00:00
return 0;
}
uint8_t lang_check(uint16_t addr)
{
uint16_t sum = 0;
uint16_t size = pgm_read_word((uint16_t*)(addr + 4));
uint16_t lt_sum = pgm_read_word((uint16_t*)(addr + 8));
uint16_t i; for (i = 0; i < size; i++)
sum += (uint16_t)pgm_read_byte((uint8_t*)(addr + i)) << ((i & 1)?0:8);
sum -= lt_sum; //subtract checksum
sum = (sum >> 8) | ((sum & 0xff) << 8); //swap bytes
return (sum == lt_sum);
}
uint8_t lang_get_count()
2018-05-28 17:41:16 +00:00
{
if (pgm_read_dword(((uint32_t*)(_PRI_LANG_SIGNATURE))) == 0xffffffff)
return 1; //signature not set - only primary language will be available
2021-03-05 07:51:23 +00:00
#ifdef XFLASH
XFLASH_SPI_ENTER();
uint8_t count = 2; //count = 1+n (primary + secondary + all in xflash)
uint32_t addr = LANG_OFFSET;
lang_table_header_t header; //table header structure
while (1)
{
2021-03-05 07:51:23 +00:00
xflash_rd_data(addr, (uint8_t*)&header, sizeof(lang_table_header_t)); //read table header from xflash
if (header.magic != LANG_MAGIC) break; //break if magic not valid
addr += header.size; //calc address of next table
count++; //inc counter
}
2021-03-05 07:51:23 +00:00
#else //XFLASH
uint16_t table = _SEC_LANG_TABLE;
uint8_t count = 1; //count = 1 (primary)
while (pgm_read_dword(((uint32_t*)table)) == LANG_MAGIC) //magic valid
{
table += pgm_read_word((uint16_t*)(table + 4));
count++;
}
2021-03-05 07:51:23 +00:00
#endif //XFLASH
return count;
2018-05-28 17:41:16 +00:00
}
uint8_t lang_get_header(uint8_t lang, lang_table_header_t* header, uint32_t* offset)
{
if (lang == LANG_ID_PRI) return 0; //primary lang not supported for this function
2021-03-05 07:51:23 +00:00
#ifdef XFLASH
if (lang == LANG_ID_SEC)
{
uint16_t ui = _SEC_LANG_TABLE; //table pointer
memcpy_P(header, (lang_table_t*)(_SEC_LANG_TABLE), sizeof(lang_table_header_t)); //read table header from progmem
if (offset) *offset = ui;
return (header->magic == LANG_MAGIC)?1:0; //return 1 if magic valid
}
2021-03-05 07:51:23 +00:00
XFLASH_SPI_ENTER();
uint32_t addr = LANG_OFFSET;
lang--;
while (1)
{
2021-03-05 07:51:23 +00:00
xflash_rd_data(addr, (uint8_t*)(header), sizeof(lang_table_header_t)); //read table header from xflash
if (header->magic != LANG_MAGIC) break; //break if not valid
if (offset) *offset = addr;
if (--lang == 0) return 1;
addr += header->size; //calc address of next table
}
2021-03-05 07:51:23 +00:00
#else //XFLASH
if (lang == LANG_ID_SEC)
{
uint16_t ui = _SEC_LANG_TABLE; //table pointer
memcpy_P(header, (lang_table_header_t*)ui, sizeof(lang_table_header_t)); //read table header from progmem
if (offset) *offset = ui;
return (header->magic == LANG_MAGIC)?1:0; //return 1 if magic valid
}
2021-03-05 07:51:23 +00:00
#endif //XFLASH
return 0;
}
uint16_t lang_get_code(uint8_t lang)
{
if (lang == LANG_ID_PRI) return LANG_CODE_EN; //primary lang = EN
2021-03-05 07:51:23 +00:00
#ifdef XFLASH
if (lang == LANG_ID_SEC)
{
uint16_t ui = _SEC_LANG_TABLE; //table pointer
if (pgm_read_dword(((uint32_t*)(ui + 0))) != LANG_MAGIC) return LANG_CODE_XX; //magic not valid
return pgm_read_word(((uint32_t*)(ui + 10))); //return lang code from progmem
}
2021-03-05 07:51:23 +00:00
XFLASH_SPI_ENTER();
uint32_t addr = LANG_OFFSET;
lang_table_header_t header; //table header structure
lang--;
while (1)
{
2021-03-05 07:51:23 +00:00
xflash_rd_data(addr, (uint8_t*)&header, sizeof(lang_table_header_t)); //read table header from xflash
if (header.magic != LANG_MAGIC) break; //break if not valid
if (--lang == 0) return header.code;
addr += header.size; //calc address of next table
}
2021-03-05 07:51:23 +00:00
#else //XFLASH
uint16_t table = _SEC_LANG_TABLE;
uint8_t count = 1; //count = 1 (primary)
while (pgm_read_dword((uint32_t*)table) == LANG_MAGIC) //magic valid
{
if (count == lang) return pgm_read_word(((uint16_t*)(table + 10))); //read language code
table += pgm_read_word((uint16_t*)(table + 4));
count++;
}
2021-03-05 07:51:23 +00:00
#endif //XFLASH
return LANG_CODE_XX;
}
const char* lang_get_name_by_code(uint16_t code)
{
switch (code)
{
case LANG_CODE_EN: return _n("English");
case LANG_CODE_CZ: return _n("Cestina");
case LANG_CODE_DE: return _n("Deutsch");
case LANG_CODE_ES: return _n("Espanol");
case LANG_CODE_FR: return _n("Francais");
case LANG_CODE_IT: return _n("Italiano");
case LANG_CODE_PL: return _n("Polski");
#ifdef COMMUNITY_LANGUAGE_SUPPORT //Community language support
#ifdef COMMUNITY_LANG_GROUP1_NL
case LANG_CODE_NL: return _n("Nederlands"); //community Dutch contribution
#endif // COMMUNITY_LANG_GROUP1_NL
2021-12-21 13:33:35 +00:00
#ifdef COMMUNITY_LANG_GROUP1_SV
case LANG_CODE_SV: return _n("Svenska"); //community Swedish contribution
#endif // COMMUNITY_LANG_GROUP1_SV
2022-02-22 10:56:01 +00:00
#ifdef COMMUNITY_LANG_GROUP1_NO
case LANG_CODE_NO: return _n("Norsk"); //community Swedish contribution
#endif // COMMUNITY_LANG_GROUP1_NO
2021-12-21 14:15:23 +00:00
#ifdef COMMUNITY_LANG_GROUP1_DA
2021-12-21 14:28:33 +00:00
case LANG_CODE_DA: return _n("Dansk"); //community Danish contribution
2021-12-21 14:15:23 +00:00
#endif // COMMUNITY_LANG_GROUP1_DA
2022-02-10 18:01:04 +00:00
#ifdef COMMUNITY_LANG_GROUP1_SK
2022-02-11 11:49:41 +00:00
case LANG_CODE_SK: return _n("Slovencina"); //community Slovak contribution
2022-02-10 18:01:04 +00:00
#endif // COMMUNITY_LANG_GROUP1_SK
2021-12-21 14:24:06 +00:00
#ifdef COMMUNITY_LANG_GROUP1_SL
case LANG_CODE_SL: return _n("Slovenscina"); //community Slovanian contribution
#endif // COMMUNITY_LANG_GROUP1_SL
2021-12-21 14:28:33 +00:00
#ifdef COMMUNITY_LANG_GROUP1_HU
case LANG_CODE_HU: return _n("Magyar"); //community Hungarian contribution
#endif // COMMUNITY_LANG_GROUP1_HU
2021-12-21 14:32:54 +00:00
#ifdef COMMUNITY_LANG_GROUP1_LB
case LANG_CODE_LB: return _n("Letzebuergesch"); //community Luxembourgish contribution
#endif // COMMUNITY_LANG_GROUP1_LB
2021-12-21 14:36:34 +00:00
#ifdef COMMUNITY_LANG_GROUP1_HR
case LANG_CODE_HR: return _n("Hrvatski"); //community Croatian contribution
#endif // COMMUNITY_LANG_GROUP1_HR
#ifdef COMMUNITY_LANG_GROUP2_LT
2022-01-03 14:00:45 +00:00
case LANG_CODE_LT: return _n("Lietuviu"); //community Lithuanian contribution
#endif // COMMUNITY_LANG_GROUP2_LT
2022-01-12 09:09:01 +00:00
#ifdef COMMUNITY_LANG_GROUP1_RO
case LANG_CODE_RO: return _n("Romana"); //community Romanian contribution
#endif // COMMUNITY_LANG_GROUP1_RO
//Use the 3 lines below as a template and replace 'QR' and 'New language'
//#ifdef COMMUNITY_LANG_GROUP1_QR
// case LANG_CODE_QR: return _n("New language"); //community contribution
//#endif // COMMUNITY_LANG_GROUP1_QR
#endif // COMMUNITY_LANGUAGE_SUPPORT
}
return _n("??");
}
void lang_reset(void)
{
lang_selected = 0;
eeprom_update_byte((unsigned char*)EEPROM_LANG, LANG_ID_FORCE_SELECTION);
}
uint8_t lang_is_selected(void)
{
uint8_t lang_eeprom = eeprom_read_byte((unsigned char*)EEPROM_LANG);
return (lang_eeprom != LANG_ID_FORCE_SELECTION) && (lang_eeprom == lang_selected);
}
#ifdef DEBUG_SEC_LANG
#include <stdio.h>
const char* lang_get_sec_lang_str_by_id(uint16_t id)
{
uint16_t ui = _SEC_LANG_TABLE; //table pointer
return ui + pgm_read_word(((uint16_t*)(ui + 16 + id * 2))); //read relative offset and return calculated pointer
}
uint16_t lang_print_sec_lang(FILE* out)
{
printf_P(_n("&_SEC_LANG = 0x%04x\n"), &_SEC_LANG);
printf_P(_n("sizeof(_SEC_LANG) = 0x%04x\n"), sizeof(_SEC_LANG));
uint16_t ptr_lang_table0 = ((uint16_t)(&_SEC_LANG) + 0xff) & 0xff00;
printf_P(_n("&_lang_table0 = 0x%04x\n"), ptr_lang_table0);
uint32_t _lt_magic = pgm_read_dword(((uint32_t*)(ptr_lang_table0 + 0)));
uint16_t _lt_size = pgm_read_word(((uint16_t*)(ptr_lang_table0 + 4)));
uint16_t _lt_count = pgm_read_word(((uint16_t*)(ptr_lang_table0 + 6)));
uint16_t _lt_chsum = pgm_read_word(((uint16_t*)(ptr_lang_table0 + 8)));
uint16_t _lt_resv0 = pgm_read_word(((uint16_t*)(ptr_lang_table0 + 10)));
uint32_t _lt_resv1 = pgm_read_dword(((uint32_t*)(ptr_lang_table0 + 12)));
printf_P(_n(" _lt_magic = 0x%08lx %S\n"), _lt_magic, (_lt_magic==LANG_MAGIC)?_n("OK"):_n("NA"));
printf_P(_n(" _lt_size = 0x%04x (%d)\n"), _lt_size, _lt_size);
printf_P(_n(" _lt_count = 0x%04x (%d)\n"), _lt_count, _lt_count);
printf_P(_n(" _lt_chsum = 0x%04x\n"), _lt_chsum);
printf_P(_n(" _lt_resv0 = 0x%04x\n"), _lt_resv0);
printf_P(_n(" _lt_resv1 = 0x%08lx\n"), _lt_resv1);
if (_lt_magic != LANG_MAGIC) return 0;
puts_P(_n(" strings:\n"));
uint16_t ui = _SEC_LANG_TABLE; //table pointer
for (ui = 0; ui < _lt_count; ui++)
fprintf_P(out, _n(" %3d %S\n"), ui, lang_get_sec_lang_str_by_id(ui));
return _lt_count;
}
#endif //DEBUG_SEC_LANG
#endif //(LANG_MODE == 0)
void lang_boot_update_start(uint8_t lang)
{
uint8_t cnt = lang_get_count();
if ((lang < 2) || (lang > cnt)) return; //only languages from xflash can be selected
bootapp_reboot_user0(lang << 3);
}