class CHECK_LOCALE_STRINGS_COMMAND

(source code)

description

A command for verifying localization translation identifiers against various kinds of source texts.

note
	description: "[
		A command for verifying localization translation identifiers against various kinds of source texts.
	]"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-09-22 16:57:25 GMT (Sunday 22nd September 2024)"
	revision: "33"

class
	CHECK_LOCALE_STRINGS_COMMAND

obsolete
	"Use EL_LOCALIZATION_TEST with EL_REFLECTIVE_LOCALE_TEXTS"

inherit
	EL_APPLICATION_COMMAND

	EL_FILE_TREE_COMMAND
		rename
			tree_dir as source_dir,
			make as make_tree_command
		redefine
			new_file_list, execute, make_default
		end

	EL_BUILDABLE_FROM_PYXIS
		redefine
			make_default, building_action_table
		end

	EL_EIFFEL_SOURCE_LINE_STATE_MACHINE
		rename
			make as make_machine
		end

	EL_MODULE_LOCALE

	EL_MODULE_LIO

	EL_LOCALE_CONSTANTS

create
	make

feature {EL_APPLICATION} -- Initialization

	make (config_path: FILE_PATH; language: STRING)
		do
			make_from_file (config_path)
			translations := Locale.new_translation_table (language)
			translations.print_duplicates
			create referenced_keys.make_equal (translations.count)
		end

	make_default
		do
			make_machine
			create evolicity_parser_list.make (0)
			create included_files.make_empty
			create last_localized_file_name_extension.make_empty
			create additional_keys_table.make_equal (7)
			create routine_lines.make_empty
			create parser.make
			create missing_keys.make_empty
			create source_path
			create localized_file_name_list.make_empty
			create localized_www_body_path_list.make_empty

			Precursor {EL_BUILDABLE_FROM_PYXIS}
			Precursor {EL_FILE_TREE_COMMAND}
		end

feature -- Constants

	Description: STRING = "Check that every localized string can be found in resources/locales"

feature -- Status query

	routine_has_english_prefix: BOOLEAN

feature -- Basic operations

	execute
		local
			l_unreferenced_items: like unreferenced_items
		do
			Precursor
			lio.put_new_line

			localized_file_name_list.do_all (agent do_with_localizeable_file (?, 2))
			localized_www_body_path_list.do_all (agent do_with_localizeable_file (?, 3))

			across additional_keys_table as list loop
				source_path := list.key
				check_keys (list.item)
				save_missing_keys (list.key)
			end

			across evolicity_parser_list as evolicity_parser loop
				evolicity_parser.item.find_all (Void)
				source_path := evolicity_parser.item.source_file_path
				check_keys (evolicity_parser.item.locale_keys)
			end

			l_unreferenced_items := unreferenced_items
			if not l_unreferenced_items.is_empty then
				lio.put_new_line
				lio.put_line ("UNREFERENCED ITEMS")
				across l_unreferenced_items as id loop
					lio.put_string_field ("id", id.item)
					lio.put_new_line
				end
			end
		end

feature {NONE} -- State handlers

	find_class_declaration (line: ZSTRING)
		do
			if code_line_is_class_declaration then
				state := agent find_routine
			end
		end

	find_routine (line: ZSTRING)
		local
			pos_quote: INTEGER
		do
			if code_line_starts_with (0, Keyword.end_) then
				state := final
			elseif code_line_starts_with_one_of (2, Routine_start_keywords) then
				routine_lines.wipe_out
				state := agent find_routine_end
			elseif tab_count = 1
				and then across English_prefixes as l_prefix some code_line.starts_with (l_prefix.item) end
			then
				if code_line [code_line.count] = '"' then
					-- eg: English_name: STRING = "&Backup"
					pos_quote := code_line.last_index_of ('"', code_line.count - 1)
					if pos_quote > 0 then
						check_keys (<< code_line.substring (pos_quote + 1, code_line.count - 1) >>)
					end
				else
					routine_has_english_prefix := True
				end
			end
		end

	find_routine_end (line: ZSTRING)
		local
			l_parser: like parser
		do
			if code_line_starts_with (2, Keyword.end_) then
				if routine_has_english_prefix then
					create {EL_ROUTINE_RESULT_LOCALE_STRING_PARSER} l_parser.make
				else
					l_parser := parser
				end
				l_parser.set_source_text (routine_lines.joined_lines)
				l_parser.find_all (Void)
				check_keys (l_parser.locale_keys)
				state := agent find_routine
				routine_has_english_prefix := False
			else
				routine_lines.extend (line)
			end
		end

feature {NONE} -- Implementation

	check_file_path_exists (file_path: FILE_PATH)
		do
			if not file_path.exists then
				lio.put_line ("FILE DOES NOT EXIST")
				lio.put_path_field ("Cannot find %S", file_path)
				lio.put_new_line
			end
		end

	check_keys (locale_keys: EL_ZSTRING_LIST)
		do
			across locale_keys as key loop
				if translations.has (key.item) then
					referenced_keys.put (key.item)
				else
					if missing_keys.is_empty then
						lio.put_path_field ("Source %S", source_path)
						lio.put_new_line
					end
					missing_keys.extend (key.item)
					lio.put_string_field ("id", key.item)
					lio.put_string (" MISSING")
					lio.put_new_line
				end
			end
		end

	do_with_file (a_source_path: FILE_PATH)
		do
			source_path := a_source_path
			do_once_with_file_lines (agent find_class_declaration, open_lines (source_path, Latin_1))
			save_missing_keys (source_path.base_name)
		end

	do_with_localizeable_file (file_path: FILE_PATH; index_of_lang_id_from_end: INTEGER)
		local
			name: ZSTRING; steps: EL_PATH_STEPS
		do
			name := file_path.without_extension.base

			source_path := file_path
			check_keys (create {EL_ZSTRING_LIST}.make_with_lines (name))

			translations.search (name)
			if translations.found then
				steps := file_path.without_extension
				steps [steps.count - index_of_lang_id_from_end + 1] := translations.language
				steps.set_base (translations.found_item + "." + file_path.extension)
				check_file_path_exists (steps)
			end
		end

	put_node_directory_error (node_name: STRING; dir_path: DIR_PATH)
		do
			lio.put_string_field ("DIRECTORY NODE", node_name)
			lio.put_new_line
			lio.put_path_field ("Cannot find %S", dir_path)
			lio.put_new_line
		end

	save_missing_keys (name: ZSTRING)
		local
			missing: EL_MISSING_TRANSLATIONS
		do
			if not missing_keys.is_empty then
				create missing.make_from_file (Workarea_pyx_template #$ [name])
				missing.class_name.append (name.as_upper)
				missing.translation_keys.merge_right (missing_keys)
				missing.serialize
			end
		end

	unreferenced_items: EL_ZSTRING_LIST
		do
			create Result.make_empty
			across translations.key_list as key loop
				if not referenced_keys.has (key.item) then
					Result.extend (key.item)
				end
			end
		end

feature {NONE} -- Factory

	new_empty_list: EL_ZSTRING_LIST
		do
			create Result.make_empty
		end

	new_file_list: EL_FILE_PATH_LIST
		do
			Result := Precursor
			Result.append (included_files)
		end

feature {NONE} -- Internal attributes

	additional_keys_table: EL_ZSTRING_HASH_TABLE [EL_ZSTRING_LIST]

	evolicity_parser_list: ARRAYED_LIST [EVOLICITY_TEMPLATE_PARSER]

	included_files: EL_FILE_PATH_LIST

	last_localized_file_name_extension: ZSTRING

	localized_file_name_list: EL_FILE_PATH_LIST

	localized_www_body_path_list: EL_FILE_PATH_LIST

	missing_keys: EL_ZSTRING_LIST

	parser: EL_ROUTINE_LOCALE_STRING_PARSER

	referenced_keys: EL_HASH_SET [ZSTRING]

	routine_lines: EL_ZSTRING_LIST

	source_path: FILE_PATH

	translations: EL_TRANSLATION_TABLE

feature {NONE} -- Build from Pyxis

	Root_node_name: STRING = "configuration"

	building_action_table: EL_PROCEDURE_TABLE [STRING]
			-- Nodes relative to root element: bix
		do
			create Result.make_assignments (<<
				["evolicity-template/ignore-key/text()",	agent do evolicity_parser_list.last.ignored_key_set.put (node) end],
				["localized-file-names/@extension",			agent do node.set (last_localized_file_name_extension) end],
				["source-dir/text()",							agent do source_dir := node.to_expanded_dir_path end],
				["include/@name",									agent do additional_keys_table.put (new_empty_list, node) end],

				["include-class/text()",						agent extend_file_list (included_files)],
				["localized-file-names/text()",				agent extend_localized_file_name_list],
				["localized-www-content/text()",				agent extend_localized_www_content],
				["evolicity-template/@path",					agent extend_evolicity_parser_list],
				["include/keys/text()",							agent extend_additional_keys_table_item]
			>>)
		end

	extend_additional_keys_table_item
		do
			if additional_keys_table.inserted then
				additional_keys_table.found_item.extend (node)
			end
		end

	extend_evolicity_parser_list
		do
			evolicity_parser_list.extend (create {EVOLICITY_TEMPLATE_PARSER}.make (node.to_expanded_file_path))
		end

	extend_file_list (list: EL_FILE_PATH_LIST)
		local
			file_path: FILE_PATH
		do
			file_path := node.to_expanded_file_path
			if file_path.exists then
				list.extend (file_path)
			else
				lio.put_path_field ("Missing", file_path)
				lio.put_new_line
			end
		end

	extend_localized_file_name_list
		local
			dir_path: DIR_PATH; l_directory: EL_DIRECTORY
		do
			dir_path := node.to_expanded_dir_path
			create l_directory.make (dir_path)
			if dir_path.exists then
				l_directory.files_with_extension (last_localized_file_name_extension).do_all (
					agent localized_file_name_list.extend
				)
			else
				put_node_directory_error ("localized-file-names", dir_path)
			end
		end

	extend_localized_www_content
		local
			dir_path: DIR_PATH; web_menu_list: EL_ZSTRING_LIST; menu_name: ZSTRING
		do
			dir_path := node.to_expanded_dir_path
			if dir_path.exists then
				across File_system.files_with_extension (dir_path, Body_extension, True) as file_path loop
					menu_name := file_path.item.parent.base
					additional_keys_table.search (Web_menu)
					if additional_keys_table.found then
						web_menu_list := additional_keys_table.found_item
					else
						create web_menu_list.make_empty
						additional_keys_table [Web_menu] := web_menu_list
					end
					if not web_menu_list.has (menu_name) then
						web_menu_list.extend (menu_name)
					end
					localized_www_body_path_list.extend (file_path.item)
				end
			else
				put_node_directory_error ("localized-www-content", dir_path)
			end
		end

feature {NONE} -- Constants

	Body_extension: ZSTRING
		once
			Result := "body"
		end

	English_prefixes: ARRAY [EL_READABLE_ZSTRING]
		local
			prefix_1, prefix_2: ZSTRING
		once
			prefix_1 := "English_"; prefix_2 := "Eng_"
			Result := << prefix_1, prefix_2 >>
		end

	File_extensions: STRING = "e"

	Web_menu: ZSTRING
		once
			Result := "Web menu"
		end

	Workarea_pyx_template: ZSTRING
		once
			Result := "workarea/localization/%S.pyx"
		end

end