From 7eac920985a67358dc0783dc7ab1a7c2a6df4471 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 13:26:55 +0100 Subject: [PATCH 1/6] move build python script to scripts folder --- Makefile | 4 ++-- echo_progress.py | 28 ---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 echo_progress.py diff --git a/Makefile b/Makefile index a659f78ac..88c8e03a0 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \ ECHO="COUNTTHIS" | grep -c "COUNTTHIS") N := x C = $(words $N)$(eval N := x $N) -ECHO = python3 $(I)/echo_progress.py --stepno=$C --nsteps=$T +ECHO = python3 $(I)/scripts/echo_progress.py --stepno=$C --nsteps=$T endif # determine number of parallel compiles based on OS @@ -151,7 +151,7 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@ .SILENT: $(OUTPUT) all: $(OUTPUT) - @$(ECHO) All done + @$(ECHO) Build complete. $(OUTPUT): $(OBJS) @mkdir -p $(@D) diff --git a/echo_progress.py b/echo_progress.py deleted file mode 100644 index 0a1a96529..000000000 --- a/echo_progress.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Print makefile progress -From https://stackoverflow.com/questions/451413/make-makefile-progress-indication -""" - -import argparse -import math -import sys - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--stepno", type=int, required=True) - parser.add_argument("--nsteps", type=int, required=True) - parser.add_argument("remainder", nargs=argparse.REMAINDER) - args = parser.parse_args() - - nchars = int(math.log(args.nsteps, 10)) + 1 - fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars)) - progress = 100 * args.stepno / args.nsteps - sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress)) - for item in args.remainder: - sys.stdout.write(" ") - sys.stdout.write(item) - sys.stdout.write("\n") - -if __name__ == "__main__": - main() - \ No newline at end of file From bae6b600bd2c47f3fad3edb8933c480fef220299 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 13:27:04 +0100 Subject: [PATCH 2/6] formatting --- scripts/app-tls-size.py | 122 ++++++++++++++++++++----------------- scripts/build_interface.py | 2 + scripts/echo_progress.py | 29 +++++++++ scripts/otatool.py | 103 ++++++++++++++++++++----------- scripts/upload.py | 2 +- 5 files changed, 163 insertions(+), 95 deletions(-) create mode 100644 scripts/echo_progress.py diff --git a/scripts/app-tls-size.py b/scripts/app-tls-size.py index 214e4a433..d7722591c 100755 --- a/scripts/app-tls-size.py +++ b/scripts/app-tls-size.py @@ -26,79 +26,87 @@ import re import subprocess import sys -RE_ELF_SECTION = re.compile(r"^\s*(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+") +RE_ELF_SECTION = re.compile( + r"^\s*(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+") Symbol = collections.namedtuple("Symbol", ["value", "size", "line"]) -RE_ELF_SYMBOL = re.compile(r"^(?P\s*(?P\w+):\s+)(?P\w+)(?P\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+))") +RE_ELF_SYMBOL = re.compile( + r"^(?P\s*(?P\w+):\s+)(?P\w+)(?P\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+))") + def print_tls_size(fw_elf): - tls_offset = None - width = 8 + tls_offset = None + width = 8 - lines = subprocess.run(["readelf", "-W", "--program-headers", fw_elf], - check=True, universal_newlines=True, stdout=subprocess.PIPE - ).stdout.strip().split("\n") + lines = subprocess.run(["readelf", "-W", "--program-headers", fw_elf], + check=True, universal_newlines=True, stdout=subprocess.PIPE + ).stdout.strip().split("\n") - for line in lines: - match = RE_ELF_SECTION.match(line) - if match: - if tls_offset is None and match["type"] == "TLS": - tls_offset = int(match["virtaddr"], 16) + for line in lines: + match = RE_ELF_SECTION.match(line) + if match: + if tls_offset is None and match["type"] == "TLS": + tls_offset = int(match["virtaddr"], 16) - header = True - lines = subprocess.run(["readelf", "-W", "--syms", "--dyn-syms", fw_elf], - check=True, universal_newlines=True, stdout=subprocess.PIPE - ).stdout.strip().split("\n") - syms = set() + header = True + lines = subprocess.run(["readelf", "-W", "--syms", "--dyn-syms", fw_elf], + check=True, universal_newlines=True, stdout=subprocess.PIPE + ).stdout.strip().split("\n") + syms = set() - for line in lines: - match = RE_ELF_SYMBOL.match(line) - if match: - header = False + for line in lines: + match = RE_ELF_SYMBOL.match(line) + if match: + header = False - if match["type"] == "TLS": - syms.add(Symbol(int(match["value"], 16), int(match["size"]), line)) - width = len(match['value']) - elif tls_offset is not None and (match["type"] == "NOTYPE" and match["bind"] == "GLOBAL" - and match["visibility"] == "DEFAULT" - and match["name"] in set(["_thread_local_start", "_thread_local_end"]) - ): - value = int(match["value"], 16) - tls_offset - line = ("{1}{2:0{0}x}{3}").format(len(match['value']), - match["before_value"], value, match["after_value"]) - syms.add(Symbol(value, int(match["size"]), line)) + if match["type"] == "TLS": + syms.add( + Symbol(int(match["value"], 16), int(match["size"]), line)) + width = len(match['value']) + elif tls_offset is not None and (match["type"] == "NOTYPE" and match["bind"] == "GLOBAL" + and match["visibility"] == "DEFAULT" + and match["name"] in set(["_thread_local_start", "_thread_local_end"]) + ): + value = int(match["value"], 16) - tls_offset + line = ("{1}{2:0{0}x}{3}").format(len(match['value']), + match["before_value"], value, match["after_value"]) + syms.add(Symbol(value, int(match["size"]), line)) - elif header: - print(line) + elif header: + print(line) - if syms: - syms = list(syms) - syms.sort() - size = (syms[-1].value + syms[-1].size) - syms[0].value - else: - size = 0 + if syms: + syms = list(syms) + syms.sort() + size = (syms[-1].value + syms[-1].size) - syms[0].value + else: + size = 0 - value = syms[0].value - for sym in syms: - if sym.value > value: - print("\t{1:0{0}x} {2:5d} TLS UNKNOWN".format(width, value, sym.value - value)) - print(sym.line) - value = sym.value + sym.size + value = syms[0].value + for sym in syms: + if sym.value > value: + print("\t{1:0{0}x} {2:5d} TLS UNKNOWN".format( + width, value, sym.value - value)) + print(sym.line) + value = sym.value + sym.size + + print() + print(f"Total Thread-Local Storage size: {size} bytes") - print() - print(f"Total Thread-Local Storage size: {size} bytes") def after_fw_elf(source, target, env): - fw_elf = str(target[0]) - print_tls_size(fw_elf) + fw_elf = str(target[0]) + print_tls_size(fw_elf) + if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Calculate size of Thread-Local Storage") - parser.add_argument("fw_elf", metavar="ELF", type=str, help="Firmware ELF filename") + parser = argparse.ArgumentParser( + description="Calculate size of Thread-Local Storage") + parser.add_argument("fw_elf", metavar="ELF", type=str, + help="Firmware ELF filename") - args = parser.parse_args() - print_tls_size(**vars(args)) + args = parser.parse_args() + print_tls_size(**vars(args)) elif __name__ == "SCons.Script": - Import("env") + Import("env") - env.AddPostAction("${BUILD_DIR}/${PROGNAME}.elf", after_fw_elf) - \ No newline at end of file + env.AddPostAction("${BUILD_DIR}/${PROGNAME}.elf", after_fw_elf) diff --git a/scripts/build_interface.py b/scripts/build_interface.py index fbbef8813..1e21f63f2 100755 --- a/scripts/build_interface.py +++ b/scripts/build_interface.py @@ -3,6 +3,7 @@ import os Import("env") + def buildWeb(): os.chdir("interface") print("Building web interface...") @@ -19,6 +20,7 @@ def buildWeb(): finally: os.chdir("..") + # Don't buuld webUI if called from GitHub Actions if "NO_BUILD_WEBUI" in os.environ: print("!! Skipping the build of the web interface !!") diff --git a/scripts/echo_progress.py b/scripts/echo_progress.py new file mode 100644 index 000000000..50fb482e6 --- /dev/null +++ b/scripts/echo_progress.py @@ -0,0 +1,29 @@ +""" +Print makefile progress +From https://stackoverflow.com/questions/451413/make-makefile-progress-indication +""" + +import argparse +import math +import sys + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--stepno", type=int, required=True) + parser.add_argument("--nsteps", type=int, required=True) + parser.add_argument("remainder", nargs=argparse.REMAINDER) + args = parser.parse_args() + + nchars = int(math.log(args.nsteps, 10)) + 1 + fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars)) + progress = 100 * args.stepno / args.nsteps + sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress)) + for item in args.remainder: + sys.stdout.write(" ") + sys.stdout.write(item) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main() diff --git a/scripts/otatool.py b/scripts/otatool.py index 75298e1bc..c16e13e15 100644 --- a/scripts/otatool.py +++ b/scripts/otatool.py @@ -18,7 +18,8 @@ import tempfile try: from parttool import PARTITION_TABLE_OFFSET, PartitionName, PartitionType, ParttoolTarget except ImportError: - COMPONENTS_PATH = os.path.expandvars(os.path.join('$IDF_PATH', 'components')) + COMPONENTS_PATH = os.path.expandvars( + os.path.join('$IDF_PATH', 'components')) PARTTOOL_DIR = os.path.join(COMPONENTS_PATH, 'partition_table') sys.path.append(PARTTOOL_DIR) from parttool import PARTITION_TABLE_OFFSET, PartitionName, PartitionType, ParttoolTarget @@ -49,7 +50,8 @@ class OtatoolTarget(): temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.close() try: - self.target.read_partition(OtatoolTarget.OTADATA_PARTITION, temp_file.name) + self.target.read_partition( + OtatoolTarget.OTADATA_PARTITION, temp_file.name) with open(temp_file.name, 'rb') as f: self.otadata = f.read() finally: @@ -101,7 +103,8 @@ class OtatoolTarget(): ota_partitions = list() for i in range(gen.NUM_PARTITION_SUBTYPE_APP_OTA): - ota_partition = filter(lambda p: p.subtype == (gen.MIN_PARTITION_SUBTYPE_APP_OTA + i), partition_table) + ota_partition = filter(lambda p: p.subtype == ( + gen.MIN_PARTITION_SUBTYPE_APP_OTA + i), partition_table) try: ota_partitions.append(list(ota_partition)[0]) @@ -118,9 +121,11 @@ class OtatoolTarget(): try: if isinstance(ota_id, int): - ota_partition_next = filter(lambda p: p.subtype - gen.MIN_PARTITION_SUBTYPE_APP_OTA == ota_id, ota_partitions) + ota_partition_next = filter( + lambda p: p.subtype - gen.MIN_PARTITION_SUBTYPE_APP_OTA == ota_id, ota_partitions) else: - ota_partition_next = filter(lambda p: p.name == ota_id, ota_partitions) + ota_partition_next = filter( + lambda p: p.name == ota_id, ota_partitions) ota_partition_next = list(ota_partition_next)[0] except IndexError: @@ -173,7 +178,8 @@ class OtatoolTarget(): try: with open(temp_file.name, 'wb') as otadata_next_file: - start = (1 if otadata_compute_base == 0 else 0) * (self.spi_flash_sec_size >> 1) + start = (1 if otadata_compute_base == 0 else 0) * \ + (self.spi_flash_sec_size >> 1) otadata_next_file.write(self.otadata) @@ -185,15 +191,18 @@ class OtatoolTarget(): otadata_next_file.flush() - self.target.write_partition(OtatoolTarget.OTADATA_PARTITION, temp_file.name) + self.target.write_partition( + OtatoolTarget.OTADATA_PARTITION, temp_file.name) finally: os.unlink(temp_file.name) def read_ota_partition(self, ota_id, output): - self.target.read_partition(self._get_partition_id_from_ota_id(ota_id), output) + self.target.read_partition( + self._get_partition_id_from_ota_id(ota_id), output) def write_ota_partition(self, ota_id, input): - self.target.write_partition(self._get_partition_id_from_ota_id(ota_id), input) + self.target.write_partition( + self._get_partition_id_from_ota_id(ota_id), input) def erase_ota_partition(self, ota_id): self.target.erase_partition(self._get_partition_id_from_ota_id(ota_id)) @@ -204,7 +213,8 @@ def _read_otadata(target): otadata_info = target._get_otadata_info() - print(' {:8s} \t {:8s} | \t {:8s} \t {:8s}'.format('OTA_SEQ', 'CRC', 'OTA_SEQ', 'CRC')) + print(' {:8s} \t {:8s} | \t {:8s} \t {:8s}'.format( + 'OTA_SEQ', 'CRC', 'OTA_SEQ', 'CRC')) print('Firmware: 0x{:08x} \t0x{:08x} | \t0x{:08x} \t 0x{:08x}'.format(otadata_info[0].seq, otadata_info[0].crc, otadata_info[1].seq, otadata_info[1].crc)) @@ -238,46 +248,64 @@ def main(): parser = argparse.ArgumentParser('ESP-IDF OTA Partitions Tool') - parser.add_argument('--quiet', '-q', help='suppress stderr messages', action='store_true') - parser.add_argument('--esptool-args', help='additional main arguments for esptool', nargs='+') - parser.add_argument('--esptool-write-args', help='additional subcommand arguments for esptool write_flash', nargs='+') - parser.add_argument('--esptool-read-args', help='additional subcommand arguments for esptool read_flash', nargs='+') - parser.add_argument('--esptool-erase-args', help='additional subcommand arguments for esptool erase_region', nargs='+') + parser.add_argument( + '--quiet', '-q', help='suppress stderr messages', action='store_true') + parser.add_argument( + '--esptool-args', help='additional main arguments for esptool', nargs='+') + parser.add_argument('--esptool-write-args', + help='additional subcommand arguments for esptool write_flash', nargs='+') + parser.add_argument('--esptool-read-args', + help='additional subcommand arguments for esptool read_flash', nargs='+') + parser.add_argument('--esptool-erase-args', + help='additional subcommand arguments for esptool erase_region', nargs='+') # There are two possible sources for the partition table: a device attached to the host # or a partition table CSV/binary file. These sources are mutually exclusive. - parser.add_argument('--port', '-p', help='port where the device to read the partition table from is attached') + parser.add_argument( + '--port', '-p', help='port where the device to read the partition table from is attached') parser.add_argument('--baud', '-b', help='baudrate to use', type=int) - parser.add_argument('--partition-table-offset', '-o', help='offset to read the partition table from', type=str) + parser.add_argument('--partition-table-offset', '-o', + help='offset to read the partition table from', type=str) parser.add_argument('--partition-table-file', '-f', help='file (CSV/binary) to read the partition table from; \ overrides device attached to specified port as the partition table source when defined') - subparsers = parser.add_subparsers(dest='operation', help='run otatool -h for additional help') + subparsers = parser.add_subparsers( + dest='operation', help='run otatool -h for additional help') spi_flash_sec_size = argparse.ArgumentParser(add_help=False) - spi_flash_sec_size.add_argument('--spi-flash-sec-size', help='value of SPI_FLASH_SEC_SIZE macro', type=str) + spi_flash_sec_size.add_argument( + '--spi-flash-sec-size', help='value of SPI_FLASH_SEC_SIZE macro', type=str) # Specify the supported operations - subparsers.add_parser('read_otadata', help='read otadata partition', parents=[spi_flash_sec_size]) + subparsers.add_parser('read_otadata', help='read otadata partition', parents=[ + spi_flash_sec_size]) subparsers.add_parser('erase_otadata', help='erase otadata partition') slot_or_name_parser = argparse.ArgumentParser(add_help=False) slot_or_name_parser_args = slot_or_name_parser.add_mutually_exclusive_group() - slot_or_name_parser_args.add_argument('--slot', help='slot number of the ota partition', type=int) - slot_or_name_parser_args.add_argument('--name', help='name of the ota partition') + slot_or_name_parser_args.add_argument( + '--slot', help='slot number of the ota partition', type=int) + slot_or_name_parser_args.add_argument( + '--name', help='name of the ota partition') - subparsers.add_parser('switch_ota_partition', help='switch otadata partition', parents=[slot_or_name_parser, spi_flash_sec_size]) + subparsers.add_parser('switch_ota_partition', help='switch otadata partition', parents=[ + slot_or_name_parser, spi_flash_sec_size]) - read_ota_partition_subparser = subparsers.add_parser('read_ota_partition', help='read contents of an ota partition', parents=[slot_or_name_parser]) - read_ota_partition_subparser.add_argument('--output', help='file to write the contents of the ota partition to', required=True) + read_ota_partition_subparser = subparsers.add_parser( + 'read_ota_partition', help='read contents of an ota partition', parents=[slot_or_name_parser]) + read_ota_partition_subparser.add_argument( + '--output', help='file to write the contents of the ota partition to', required=True) - write_ota_partition_subparser = subparsers.add_parser('write_ota_partition', help='write contents to an ota partition', parents=[slot_or_name_parser]) - write_ota_partition_subparser.add_argument('--input', help='file whose contents to write to the ota partition') + write_ota_partition_subparser = subparsers.add_parser( + 'write_ota_partition', help='write contents to an ota partition', parents=[slot_or_name_parser]) + write_ota_partition_subparser.add_argument( + '--input', help='file whose contents to write to the ota partition') - subparsers.add_parser('erase_ota_partition', help='erase contents of an ota partition', parents=[slot_or_name_parser]) + subparsers.add_parser( + 'erase_ota_partition', help='erase contents of an ota partition', parents=[slot_or_name_parser]) args = parser.parse_args() @@ -298,7 +326,8 @@ def main(): target_args['partition_table_file'] = args.partition_table_file if args.partition_table_offset: - target_args['partition_table_offset'] = int(args.partition_table_offset, 0) + target_args['partition_table_offset'] = int( + args.partition_table_offset, 0) try: if args.spi_flash_sec_size: @@ -324,7 +353,7 @@ def main(): target = OtatoolTarget(**target_args) # Create the operation table and execute the operation - common_args = {'target':target} + common_args = {'target': target} ota_id = [] @@ -338,18 +367,18 @@ def main(): pass otatool_ops = { - 'read_otadata':(_read_otadata, []), - 'erase_otadata':(_erase_otadata, []), - 'switch_ota_partition':(_switch_ota_partition, ota_id), - 'read_ota_partition':(_read_ota_partition, ['output'] + ota_id), - 'write_ota_partition':(_write_ota_partition, ['input'] + ota_id), - 'erase_ota_partition':(_erase_ota_partition, ota_id) + 'read_otadata': (_read_otadata, []), + 'erase_otadata': (_erase_otadata, []), + 'switch_ota_partition': (_switch_ota_partition, ota_id), + 'read_ota_partition': (_read_ota_partition, ['output'] + ota_id), + 'write_ota_partition': (_write_ota_partition, ['input'] + ota_id), + 'erase_ota_partition': (_erase_ota_partition, ota_id) } (op, op_args) = otatool_ops[args.operation] for op_arg in op_args: - common_args.update({op_arg:vars(args)[op_arg]}) + common_args.update({op_arg: vars(args)[op_arg]}) try: common_args['ota_id'] = common_args.pop('name') diff --git a/scripts/upload.py b/scripts/upload.py index 988f881c3..f0d7322d7 100644 --- a/scripts/upload.py +++ b/scripts/upload.py @@ -161,6 +161,6 @@ def on_upload(source, target, env): print() + if env.get('UPLOAD_PROTOCOL') == 'custom': env.Replace(UPLOADCMD=on_upload) - \ No newline at end of file From ea9b6b3e005249851599ea1780a75b032368a1da Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 13:27:14 +0100 Subject: [PATCH 3/6] removed unused file --- package.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 package.json diff --git a/package.json b/package.json deleted file mode 100644 index f79f3452f..000000000 --- a/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "packageManager": "yarn@4.6.0" -} From d03ab7a16fd0bdb02077473955fcde87731cf284 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 15:13:06 +0100 Subject: [PATCH 4/6] formatting --- mock-api/rest_server.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index c5fb9152a..8d7106732 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -269,10 +269,10 @@ function updateMask(entity: any, de: any, dd: any) { const old_custom_name = dd.nodes[dd_objIndex].cn; console.log( 'comparing names, old (' + - old_custom_name + - ') with new (' + - new_custom_name + - ')' + old_custom_name + + ') with new (' + + new_custom_name + + ')' ); if (old_custom_name !== new_custom_name) { changed = true; @@ -367,15 +367,15 @@ function check_upgrade(version: string) { const stable_version = version.split(',')[1]; console.log( 'latest dev version: ' + - dev_version + - ', latest stable version: ' + - stable_version + dev_version + + ', latest stable version: ' + + stable_version ); console.log( 'Version upgrade check from version ' + - THIS_VERSION + - ', upgradable: ' + - VERSION_IS_UPGRADEABLE + THIS_VERSION + + ', upgradable: ' + + VERSION_IS_UPGRADEABLE ); data = { emsesp_version: THIS_VERSION, @@ -5049,20 +5049,21 @@ router }); // Mock GitHub API +// https://api.github.com/repos/emsesp/EMS-ESP32/releases router .get(GH_ENDPOINT_ROOT + '/tags/latest', () => { const data = { name: 'v' + LATEST_DEV_VERSION, - published_at: new Date().toISOString() + published_at: new Date().toISOString() // use todays date }; - console.log('returning latest development version: ', data); + console.log('returning latest development version (today): ', data); return data; }) .get(GH_ENDPOINT_ROOT + '/latest', () => { const data = { name: 'v' + LATEST_STABLE_VERSION, - published_at: '2025-02-07T20:09:46Z' + published_at: '2025-03-01T13:29:13.999Z' }; console.log('returning latest stable version: ', data); return data; From edb30931ae616097ba14c182850c391e8edd1d08 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 15:13:16 +0100 Subject: [PATCH 5/6] remove days_Ago --- interface/src/i18n/cz/index.ts | 1 - interface/src/i18n/de/index.ts | 1 - interface/src/i18n/en/index.ts | 1 - interface/src/i18n/fr/index.ts | 1 - interface/src/i18n/it/index.ts | 1 - interface/src/i18n/nl/index.ts | 1 - interface/src/i18n/no/index.ts | 1 - interface/src/i18n/pl/index.ts | 1 - interface/src/i18n/sk/index.ts | 1 - interface/src/i18n/sv/index.ts | 1 - interface/src/i18n/tr/index.ts | 1 - 11 files changed, 11 deletions(-) diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts index 3060a2f84..c77a54e0c 100644 --- a/interface/src/i18n/cz/index.ts +++ b/interface/src/i18n/cz/index.ts @@ -351,7 +351,6 @@ const cz: Translation = { RELEASE_TYPE: 'Typ sestavení', REINSTALL: 'Přeinstalovat', INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení', - DAYS_AGO: '{0} dn{{y|í|í|í|í|í}} zpátky' }; export default cz; diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index e1761b424..2c76b6e26 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -351,7 +351,6 @@ const de: Translation = { RELEASE_TYPE: 'Release Typ', REINSTALL: 'Neu installieren', INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung', - DAYS_AGO: '{0} Tag{{e}} vorher' }; export default de; diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 793bf9c1c..895a1eb0d 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -351,7 +351,6 @@ const en: Translation = { RELEASE_TYPE: 'Release Type', REINSTALL: 'Re-install', INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', - DAYS_AGO: '{0} day{{s}} ago' }; export default en; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index c95e406e8..5391c9695 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -351,7 +351,6 @@ const fr: Translation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', - DAYS_AGO: '{0} jour{{s}} avant' }; export default fr; diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 6ad92c913..6a396a254 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -351,7 +351,6 @@ const it: Translation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', - DAYS_AGO: '{0} giorni{{s}} fa' }; export default it; diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index f2e14992e..64f7f2329 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -351,7 +351,6 @@ const nl: Translation = { RELEASE_TYPE: 'Release Typ', REINSTALL: 'Opnieuw Installeren', INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade', - DAYS_AGO: '{0} dag{{en}} geleden' }; export default nl; \ No newline at end of file diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index e3970eeec..76ff8c327 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -351,7 +351,6 @@ const no: Translation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', - DAYS_AGO: '{0} dag{{er}} siden' }; export default no; diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 9b4c2c448..035596252 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -351,7 +351,6 @@ const pl: BaseTranslation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate - DAYS_AGO: '{0} dzień{{s}} temu' }; export default pl; diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index b8aa991bc..5f76534ec 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -351,7 +351,6 @@ const sk: Translation = { RELEASE_TYPE: 'Typ vydania', REINSTALL: 'Preinštalovať', INTERNET_CONNECTION_REQUIRED: 'Internetové pripojenie je potrebné pre automatickú kontrolu a aktualizáciu', - DAYS_AGO: 'pred {0} d{{ňami|eň|ní|ní|ní|ní}}' }; export default sk; diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 888318719..6e0b4d234 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -351,7 +351,6 @@ const sv: Translation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internetanslutning krävs för automatisk version kontroll och uppdatering', - DAYS_AGO: '{0} dag{{ar}} sedan' }; export default sv; diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index f9dbcf1b8..3f2d6e089 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -351,7 +351,6 @@ const tr: Translation = { RELEASE_TYPE: 'Release Type', // TODO translate REINSTALL: 'Re-install', // TODO translate INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate - DAYS_AGO: '{0} gün{{ü|ü|ü|ü|ü|ü}} önce' }; export default tr; From 0a92d455c8277ec9643af9af205894e4537c60ea Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 2 Mar 2025 15:13:56 +0100 Subject: [PATCH 6/6] show localized elapsed time --- interface/src/app/settings/Version.tsx | 62 +++++++++++++++++--------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/interface/src/app/settings/Version.tsx b/interface/src/app/settings/Version.tsx index 179068798..b852bcacf 100644 --- a/interface/src/app/settings/Version.tsx +++ b/interface/src/app/settings/Version.tsx @@ -31,7 +31,7 @@ import { FormLoader, SectionContent, useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; const Version = () => { - const { LL } = useI18nContext(); + const { LL, locale } = useI18nContext(); const [restarting, setRestarting] = useState(false); const [openInstallDialog, setOpenInstallDialog] = useState(false); const [usingDevVersion, setUsingDevVersion] = useState(false); @@ -92,6 +92,30 @@ const Version = () => { } }, [latestVersion, latestDevVersion]); + const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); + const DIVISIONS = [ + { amount: 60, name: 'seconds' }, + { amount: 60, name: 'minutes' }, + { amount: 24, name: 'hours' }, + { amount: 7, name: 'days' }, + { amount: 4.34524, name: 'weeks' }, + { amount: 12, name: 'months' }, + { amount: Number.POSITIVE_INFINITY, name: 'years' } + ]; + function formatTimeAgo(date) { + let duration = (date.getTime() - new Date().getTime()) / 1000; + for (let i = 0; i < DIVISIONS.length; i++) { + const division = DIVISIONS[i]; + if (Math.abs(duration) < division.amount) { + return rtf.format( + Math.round(duration), + division.name as Intl.RelativeTimeFormatUnit + ); + } + duration /= division.amount; + } + } + const getBinURL = () => { if (!internetLive) { return ''; @@ -274,7 +298,7 @@ const Version = () => { { }} /> } + slotProps={{ + typography: { + color: 'grey' + } + }} checked={!isDev} label={LL.STABLE()} - sx={{ '& .MuiSvgIcon-root': { fontSize: 18 } }} + sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }} /> { }} /> } + slotProps={{ + typography: { + color: 'grey' + } + }} checked={isDev} label={LL.DEVELOPMENT()} - sx={{ '& .MuiSvgIcon-root': { fontSize: 18 } }} + sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }} /> @@ -332,14 +366,7 @@ const Version = () => { {latestVersion.published_at && (  ( - {LL.DAYS_AGO( - Math.floor( - (Date.now() - - new Date(latestVersion.published_at).getTime()) / - (1000 * 60 * 60 * 24) - ) - )} - ) + {formatTimeAgo(new Date(latestVersion.published_at))}) )} {!usingDevVersion && showButtons(false)} @@ -357,14 +384,7 @@ const Version = () => { {latestDevVersion.published_at && (  ( - {LL.DAYS_AGO( - Math.floor( - (Date.now() - - new Date(latestDevVersion.published_at).getTime()) / - (1000 * 60 * 60 * 24) - ) - )} - ) + {formatTimeAgo(new Date(latestDevVersion.published_at))}) )} {showButtons(true)}