alchemy_i18n 5.0.2 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de03b383bd5c0f614118bc14f82ac28d203b8314ee6913b6822914bb1960086e
4
- data.tar.gz: e9b95b87c9276d1a0358a6eebe1496b95325404dfdfdf6e77bd214244af9beda
3
+ metadata.gz: b673fa605827b72a338cb5705d976eeb3cb6cfc1ceedc0f8d83e5f7a2be70591
4
+ data.tar.gz: 35ffeec42fe5b7eeffb1fbb6b4c67ff8a35faa98f4495c0f291962fa7e079dd2
5
5
  SHA512:
6
- metadata.gz: efc1fbdf5fe195ce01b224e923813120279cc4e92718b5606e1c7f8cbd442300ffc07417552cb67aab10da4c8235bd4aff043c2ce9667cdfd5ebbf4f9c82b863
7
- data.tar.gz: 9705af38be374dcc8fb9383bd6512879004fb2ea17a646109d789ebb7b7fbe37d376135d6d7231b878af7f88539e924e45f71d0213c1bba0271e028da9d12a97
6
+ metadata.gz: 2dbfb604ff8191cbebb82e6358dc37457972fe50c327747ac4eb9b981ccdeb11720de87274bf432dfc3936c1d53ee7d75d8675e74ab84158371a4427138357a7
7
+ data.tar.gz: '0758bc0a5c3fd74b2e7c372c8a039d7e5a0b13490541ac92ad17d0a5a2cf16577b638f5d7797f0de01d377b1d42e42381f5fc555e8d9cfd8187c2f3750b71ea8'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.2.0 (2026-04-13)
4
+
5
+ <!-- Release notes generated using configuration in .github/release.yml at main -->
6
+
7
+ ## What's Changed
8
+ ### Other Changes
9
+ * Translations for schedule element button by @tvdeyen in https://github.com/AlchemyCMS/alchemy_i18n/pull/88
10
+ * Add XML to show_page_in_sitemap translations across all locales by @antwertinger in https://github.com/AlchemyCMS/alchemy_i18n/pull/92
11
+ * Add missing Alchemy 8.2 translations by @tvdeyen in https://github.com/AlchemyCMS/alchemy_i18n/pull/93
12
+
13
+ ## New Contributors
14
+ * @antwertinger made their first contribution in https://github.com/AlchemyCMS/alchemy_i18n/pull/92
15
+
16
+ **Full Changelog**: https://github.com/AlchemyCMS/alchemy_i18n/compare/v5.1.0...v8.2.0
17
+
18
+ ## 5.1.0 (2026-02-18)
19
+
20
+ ## What's Changed
21
+ * Add German publish button translations by @tvdeyen in https://github.com/AlchemyCMS/alchemy_i18n/pull/89
22
+
23
+
24
+ **Full Changelog**: https://github.com/AlchemyCMS/alchemy_i18n/compare/v5.0.2...v5.1.0
25
+
3
26
  ## 5.0.2 (2026-01-28)
4
27
 
5
28
  ## What's Changed
@@ -36,4 +36,6 @@ Alchemy.translations = {
36
36
  },
37
37
  Yes: "Ja",
38
38
  No: "Nein",
39
+ "Preview failed to load":
40
+ "Die Vorschau konnte nicht geladen werden. Bitte versuchen Sie es erneut.",
39
41
  }
@@ -1,3 +1,3 @@
1
1
  module AlchemyI18n
2
- VERSION = "5.0.2"
2
+ VERSION = "8.2.0"
3
3
  end
@@ -0,0 +1,169 @@
1
+ require "yaml"
2
+
3
+ namespace :translations do
4
+ ALCHEMY_CMS_PATH = ENV.fetch("ALCHEMY_CMS_PATH", File.expand_path("~/code/alchemy_cms"))
5
+ EN_LOCALE_PATH = File.join(ALCHEMY_CMS_PATH, "config/locales/alchemy.en.yml")
6
+ LOCALES_DIR = File.expand_path("../../locales", __dir__)
7
+
8
+ LOCALE_FILES = Dir.glob(File.join(LOCALES_DIR, "alchemy.*.yml")).sort
9
+
10
+ def flatten_keys(hash, prefix = "")
11
+ hash.each_with_object({}) do |(key, value), result|
12
+ full_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
13
+ if value.is_a?(Hash)
14
+ result.merge!(flatten_keys(value, full_key))
15
+ else
16
+ result[full_key] = value
17
+ end
18
+ end
19
+ end
20
+
21
+ def load_en_keys
22
+ en = YAML.load_file(EN_LOCALE_PATH, aliases: true)
23
+ flatten_keys(en["en"])
24
+ end
25
+
26
+ def load_locale_keys(file)
27
+ locale_code = File.basename(file, ".yml").sub("alchemy.", "")
28
+ data = YAML.load_file(file, aliases: true)
29
+ [locale_code, flatten_keys(data[locale_code])]
30
+ end
31
+
32
+ # Extracts translation keys from Alchemy.t() calls, resolving scopes.
33
+ # All Alchemy.t() calls are automatically scoped under "alchemy.",
34
+ # and additional scope: parameters are appended.
35
+ #
36
+ # Examples:
37
+ # Alchemy.t(:hello) => "alchemy.hello"
38
+ # Alchemy.t(:name, scope: :filters) => "alchemy.filters.name"
39
+ # Alchemy.t(:all, scope: [:filters, :page]) => "alchemy.filters.page.all"
40
+ # Alchemy.t("Welcome") => "alchemy.Welcome"
41
+ def used_translation_keys
42
+ keys = []
43
+ # Match full Alchemy.t(...) calls, capturing the entire argument list
44
+ call_pattern = /Alchemy\.t\(([^)]+)\)/
45
+
46
+ Dir.glob(File.join(ALCHEMY_CMS_PATH, "{app,lib}", "**", "*.{rb,erb}")).each do |file|
47
+ content = File.read(file)
48
+ content.scan(call_pattern) do |args_str,|
49
+ # Skip calls with interpolation
50
+ next if args_str.include?('#{')
51
+
52
+ # Extract the key (first argument)
53
+ key = case args_str
54
+ when /\A:(\w+[?!]?)/ then $1
55
+ when /\A"([^"]+)"/ then $1
56
+ when /\A'([^']+)'/ then $1
57
+ else next
58
+ end
59
+
60
+ # Extract scope if present, but only from this call (not nested Alchemy.t calls)
61
+ scope_source = args_str.sub(/Alchemy\.t\(.*/, "")
62
+ scope_parts = ["alchemy"]
63
+ if scope_source =~ /scope:\s*\[([^\]]+)\]/
64
+ scope_content = $1
65
+ # Skip if scope contains dynamic parts (variables, method calls)
66
+ # Only symbols (:foo) and strings ("foo"/'foo') are static
67
+ next unless scope_content.strip.split(/\s*,\s*/).all? { |part|
68
+ part =~ /\A:\w+\z/ || part =~ /\A["'][^"']+["']\z/
69
+ }
70
+ scope_content.scan(/(?::(\w+)|"([^"]+)"|'([^']+)')/).each do |sym, dq, sq|
71
+ scope_parts << (sym || dq || sq)
72
+ end
73
+ elsif scope_source =~ /scope:\s*:(\w+)/
74
+ scope_parts << $1 unless $1 == "alchemy"
75
+ elsif scope_source =~ /scope:\s*['"]([^'"]+)['"]/
76
+ scope_parts << $1
77
+ end
78
+
79
+ keys << (scope_parts + [key]).join(".")
80
+ end
81
+ end
82
+
83
+ keys.uniq.sort
84
+ end
85
+
86
+ desc "Show missing translations for a locale (e.g. rake translations:missing[de])"
87
+ task :missing, [:locale] do |_, args|
88
+ locale = args[:locale]
89
+
90
+ unless File.exist?(EN_LOCALE_PATH)
91
+ abort "ERROR: English locale not found at #{EN_LOCALE_PATH}\nSet ALCHEMY_CMS_PATH to your alchemy_cms checkout."
92
+ end
93
+
94
+ file = File.join(LOCALES_DIR, "alchemy.#{locale}.yml")
95
+ abort "ERROR: Locale file not found: #{file}" unless File.exist?(file)
96
+
97
+ en_flat = load_en_keys
98
+ _, locale_flat = load_locale_keys(file)
99
+
100
+ # Keys from EN locale file missing in target
101
+ missing_from_en = (en_flat.keys - locale_flat.keys).reject { |k| en_flat[k].nil? }
102
+
103
+ # Keys used in code but not in target locale
104
+ used_keys = used_translation_keys
105
+ missing_from_code = used_keys.reject { |k| locale_flat.key?(k) }
106
+
107
+ # Exclude keys already reported as missing from EN
108
+ missing_from_en_set = missing_from_en.to_set
109
+ missing_from_code.reject! { |k| missing_from_en_set.include?(k) }
110
+
111
+
112
+ if missing_from_en.any?
113
+ puts "=== Missing from EN locale file (#{missing_from_en.size}) ==="
114
+ missing_from_en.sort.each { |k| puts " #{k}: #{en_flat[k].inspect}" }
115
+ puts
116
+ end
117
+
118
+ if missing_from_code.any?
119
+ puts "=== Used in code but missing (#{missing_from_code.size}) ==="
120
+ missing_from_code.sort.each { |k| puts " #{k}" }
121
+ puts
122
+ end
123
+
124
+ if missing_from_en.empty? && missing_from_code.empty?
125
+ puts "No missing translations for #{locale}!"
126
+ end
127
+ end
128
+
129
+ desc "Show missing translation summary for all locales"
130
+ task :status do
131
+ unless File.exist?(EN_LOCALE_PATH)
132
+ abort "ERROR: English locale not found at #{EN_LOCALE_PATH}\nSet ALCHEMY_CMS_PATH to your alchemy_cms checkout."
133
+ end
134
+
135
+ en_flat = load_en_keys
136
+ en_count = en_flat.reject { |_, v| v.nil? }.size
137
+
138
+ puts "Reference: EN locale (#{en_count} keys)"
139
+ puts "-" * 50
140
+
141
+ LOCALE_FILES.each do |file|
142
+ locale_code, locale_flat = load_locale_keys(file)
143
+ missing = (en_flat.keys - locale_flat.keys).reject { |k| en_flat[k].nil? }
144
+ extra = locale_flat.keys - en_flat.keys
145
+ pct = ((en_count - missing.size).to_f / en_count * 100).round(1)
146
+ puts "#{locale_code.ljust(6)} #{locale_flat.size} keys, #{missing.size} missing, #{extra.size} extra (#{pct}%)"
147
+ end
148
+ end
149
+
150
+ desc "Validate YAML syntax for all locale files"
151
+ task :validate do
152
+ errors = []
153
+ LOCALE_FILES.each do |file|
154
+ begin
155
+ YAML.load_file(file, aliases: true)
156
+ rescue => e
157
+ errors << "#{File.basename(file)}: #{e.message}"
158
+ end
159
+ end
160
+
161
+ if errors.any?
162
+ puts "YAML errors found:"
163
+ errors.each { |e| puts " #{e}" }
164
+ exit 1
165
+ else
166
+ puts "All #{LOCALE_FILES.size} locale files are valid YAML."
167
+ end
168
+ end
169
+ end