languageImport.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python3
  2. """
  3. languageImport.py
  4. Import LCD language strings from a CSV file or Google Sheets
  5. and write Marlin LCD language files based on the data.
  6. Use languageExport.py to export CSV from the language files.
  7. Google Sheets Link:
  8. https://docs.google.com/spreadsheets/d/12yiy-kS84ajKFm7oQIrC4CF8ZWeu9pAR4zrgxH4ruk4/edit#gid=84528699
  9. TODO: Use the defines and comments above the namespace from existing language files.
  10. Get the 'constexpr uint8_t CHARSIZE' from existing language files.
  11. Get the correct 'using namespace' for languages that don't inherit from English.
  12. """
  13. import sys, re, requests, csv, datetime
  14. #from languageUtil import namebyid
  15. LANGHOME = "Marlin/src/lcd/language"
  16. OUTDIR = 'out-language'
  17. # Get the file path from the command line
  18. FILEPATH = sys.argv[1] if len(sys.argv) > 1 else None
  19. download = FILEPATH == 'download'
  20. if not FILEPATH or download:
  21. SHEETID = "12yiy-kS84ajKFm7oQIrC4CF8ZWeu9pAR4zrgxH4ruk4"
  22. FILEPATH = 'https://docs.google.com/spreadsheet/ccc?key=%s&output=csv' % SHEETID
  23. if FILEPATH.startswith('http'):
  24. response = requests.get(FILEPATH)
  25. assert response.status_code == 200, 'GET failed for %s' % FILEPATH
  26. csvdata = response.content.decode('utf-8')
  27. else:
  28. if not FILEPATH.endswith('.csv'): FILEPATH += '.csv'
  29. with open(FILEPATH, 'r', encoding='utf-8') as f: csvdata = f.read()
  30. if not csvdata:
  31. print("Error: couldn't read CSV data from %s" % FILEPATH)
  32. exit(1)
  33. if download:
  34. DLNAME = sys.argv[2] if len(sys.argv) > 2 else 'languages.csv'
  35. if not DLNAME.endswith('.csv'): DLNAME += '.csv'
  36. with open(DLNAME, 'w', encoding='utf-8') as f: f.write(csvdata)
  37. print("Downloaded %s from %s" % (DLNAME, FILEPATH))
  38. exit(0)
  39. lines = csvdata.splitlines()
  40. print(lines)
  41. reader = csv.reader(lines, delimiter=',')
  42. gothead = False
  43. columns = ['']
  44. numcols = 0
  45. strings_per_lang = {}
  46. for row in reader:
  47. if not gothead:
  48. gothead = True
  49. numcols = len(row)
  50. if row[0] != 'name':
  51. print('Error: first column should be "name"')
  52. exit(1)
  53. # The rest of the columns are language codes and names
  54. for i in range(1, numcols):
  55. elms = row[i].split(' ')
  56. lang = elms[0]
  57. style = ('Wide' if elms[-1] == '(wide)' else 'Tall' if elms[-1] == '(tall)' else 'Narrow')
  58. columns.append({ 'lang': lang, 'style': style })
  59. if not lang in strings_per_lang: strings_per_lang[lang] = {}
  60. if not style in strings_per_lang[lang]: strings_per_lang[lang][style] = {}
  61. continue
  62. # Add the named string for all the included languages
  63. name = row[0]
  64. for i in range(1, numcols):
  65. str_key = row[i]
  66. if str_key:
  67. col = columns[i]
  68. strings_per_lang[col['lang']][col['style']][name] = str_key
  69. # Create a folder for the imported language outfiles
  70. from pathlib import Path
  71. Path.mkdir(Path(OUTDIR), exist_ok=True)
  72. FILEHEADER = '''
  73. /**
  74. * Marlin 3D Printer Firmware
  75. * Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  76. *
  77. * Based on Sprinter and grbl.
  78. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  79. *
  80. * This program is free software: you can redistribute it and/or modify
  81. * it under the terms of the GNU General Public License as published by
  82. * the Free Software Foundation, either version 3 of the License, or
  83. * (at your option) any later version.
  84. *
  85. * This program is distributed in the hope that it will be useful,
  86. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  87. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  88. * GNU General Public License for more details.
  89. *
  90. * You should have received a copy of the GNU General Public License
  91. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  92. *
  93. */
  94. #pragma once
  95. /**
  96. * %s
  97. *
  98. * LCD Menu Messages
  99. * See also https://marlinfw.org/docs/development/lcd_language.html
  100. *
  101. * Substitutions are applied for the following characters when used in menu items titles:
  102. *
  103. * $ displays an inserted string
  104. * { displays '0'....'10' for indexes 0 - 10
  105. * ~ displays '1'....'11' for indexes 0 - 10
  106. * * displays 'E1'...'E11' for indexes 0 - 10 (By default. Uses LCD_FIRST_TOOL)
  107. * @ displays an axis name such as XYZUVW, or E for an extruder
  108. */
  109. '''
  110. # Iterate over the languages which correspond to the columns
  111. # The columns are assumed to be grouped by language in the order Narrow, Wide, Tall
  112. # TODO: Go through lang only, then impose the order Narrow, Wide, Tall.
  113. # So if something is missing or out of order everything still gets built correctly.
  114. f = None
  115. gotlang = {}
  116. for i in range(1, numcols):
  117. #if i > 6: break # Testing
  118. col = columns[i]
  119. lang, style = col['lang'], col['style']
  120. # If we haven't already opened a file for this language, do so now
  121. if not lang in gotlang:
  122. gotlang[lang] = {}
  123. if f: f.close()
  124. fn = "%s/language_%s.h" % (OUTDIR, lang)
  125. f = open(fn, 'w', encoding='utf-8')
  126. if not f:
  127. print("Failed to open %s." % fn)
  128. exit(1)
  129. # Write the opening header for the new language file
  130. #f.write(FILEHEADER % namebyid(lang))
  131. f.write('/**\n * Imported from %s on %s at %s\n */\n' % (FILEPATH, datetime.date.today(), datetime.datetime.now().strftime("%H:%M:%S")))
  132. # Start a namespace for the language and style
  133. f.write('\nnamespace Language%s_%s {\n' % (style, lang))
  134. # Wide and tall namespaces inherit from the others
  135. if style == 'Wide':
  136. f.write(' using namespace LanguageNarrow_%s;\n' % lang)
  137. f.write(' #if LCD_WIDTH >= 20 || HAS_DWIN_E3V2\n')
  138. elif style == 'Tall':
  139. f.write(' using namespace LanguageWide_%s;\n' % lang)
  140. f.write(' #if LCD_HEIGHT >= 4\n')
  141. elif lang != 'en':
  142. f.write(' using namespace Language_en; // Inherit undefined strings from English\n')
  143. # Formatting for the lines
  144. indent = ' ' if style == 'Narrow' else ' '
  145. width = 34 if style == 'Narrow' else 32
  146. lstr_fmt = '%sLSTR %%-%ds = %%s;%%s\n' % (indent, width)
  147. # Emit all the strings for this language and style
  148. for name in strings_per_lang[lang][style].keys():
  149. # Get the raw string value
  150. val = strings_per_lang[lang][style][name]
  151. # Count the number of bars
  152. if val.startswith('|'):
  153. bars = val.count('|')
  154. val = val[1:]
  155. else:
  156. bars = 0
  157. # Escape backslashes, substitute quotes, and wrap in _UxGT("...")
  158. val = '_UxGT("%s")' % val.replace('\\', '\\\\').replace('"', '$$$')
  159. # Move named references outside of the macro
  160. val = re.sub(r'\(([A-Z0-9]+_[A-Z0-9_]+)\)', r'") \1 _UxGT("', val)
  161. # Remove all empty _UxGT("") that result from the above
  162. val = re.sub(r'\s*_UxGT\(""\)\s*', '', val)
  163. # No wrapper needed for just spaces
  164. val = re.sub(r'_UxGT\((" +")\)', r'\1', val)
  165. # Multi-line strings start with a bar...
  166. if bars:
  167. # Wrap the string in MSG_#_LINE(...) and split on bars
  168. val = re.sub(r'^_UxGT\((.+)\)', r'_UxGT(MSG_%s_LINE(\1))' % bars, val)
  169. val = val.replace('|', '", "')
  170. # Restore quotes inside the string
  171. val = val.replace('$$$', '\\"')
  172. # Add a comment with the English string for reference
  173. comm = ''
  174. if lang != 'en' and 'en' in strings_per_lang:
  175. en = strings_per_lang['en']
  176. if name in en[style]: str_key = en[style][name]
  177. elif name in en['Narrow']: str_key = en['Narrow'][name]
  178. if str_key:
  179. cfmt = '%%%ss// %%s' % (50 - len(val) if len(val) < 50 else 1)
  180. comm = cfmt % (' ', str_key)
  181. # Write out the string definition
  182. f.write(lstr_fmt % (name, val, comm))
  183. if style == 'Wide' or style == 'Tall': f.write(' #endif\n')
  184. f.write('}\n') # End namespace
  185. # Assume the 'Tall' namespace comes last
  186. if style == 'Tall': f.write('\nnamespace Language_%s {\n using namespace LanguageTall_%s;\n}\n' % (lang, lang))
  187. # Close the last-opened output file
  188. if f: f.close()