jekyll-open-sdg-plugins 1.0.0 → 1.1.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.
@@ -1,66 +1,94 @@
1
- # Simple collection of helper functions for use in these plugins.
2
-
3
- # Takes a translation key and returns a translated string according to the
4
- # language of the current page. Or if none is found, returns the original
5
- # key.
6
- def opensdg_translate_key(key, translations, language)
7
-
8
- # Safety code - abort now if key is nil.
9
- if key.nil?
10
- return ""
11
- end
12
-
13
- # Also make sure it is a string, and other just return it.
14
- if not key.is_a? String
15
- return key
16
- end
17
-
18
- # More safety code - abort now if key is empty.
19
- if key.empty?
20
- return ""
21
- end
22
-
23
- # Keep track of the last thing we drilled to.
24
- drilled = translations[language]
25
-
26
- # Keep track of how many levels we have drilled.
27
- levels_drilled = 0
28
- levels = key.split('.')
29
-
30
- # Loop through each level.
31
- levels.each do |level|
32
-
33
- # If we have drilled down to a scalar value too soon, abort.
34
- break if drilled.class != Hash
35
-
36
- if drilled.has_key? level
37
- # If we find something, continue drilling.
38
- drilled = drilled[level]
39
- levels_drilled += 1
40
- end
41
-
42
- end
43
-
44
- # If we didn't drill the right number of levels, return the
45
- # original string.
46
- if levels.length != levels_drilled
47
- return key
48
- end
49
-
50
- # Otherwise we must have drilled all they way.
51
- return drilled
52
- end
53
-
54
- # Takes a site object and decides whether it is using translated builds.
55
- def opensdg_translated_builds(site)
56
- # Assume the site is using translated builds.
57
- translated_builds = true
58
- site.config['languages'].each do |language|
59
- # If any languages don't have a key in site.data, the site is not using
60
- # translated builds.
61
- if !site.data.has_key? language
62
- translated_builds = false
63
- end
64
- end
65
- return translated_builds
66
- end
1
+ # Simple collection of helper functions for use in these plugins.
2
+
3
+ require "jekyll"
4
+
5
+ # Takes a translation key and returns a translated string according to the
6
+ # language of the current page. Or if none is found, returns the original
7
+ # key.
8
+ def opensdg_translate_key(key, translations, language)
9
+
10
+ # Safety code - abort now if key is nil.
11
+ if key.nil?
12
+ return ""
13
+ end
14
+
15
+ # Also make sure it is a string, and other just return it.
16
+ if not key.is_a? String
17
+ return key
18
+ end
19
+
20
+ # More safety code - abort now if key is empty.
21
+ if key.empty?
22
+ return ""
23
+ end
24
+
25
+ # Keep track of the last thing we drilled to.
26
+ drilled = translations[language]
27
+
28
+ # Keep track of how many levels we have drilled.
29
+ levels_drilled = 0
30
+ levels = key.split('.')
31
+
32
+ # Loop through each level.
33
+ levels.each do |level|
34
+
35
+ # If we have drilled down to a scalar value too soon, abort.
36
+ break if drilled.class != Hash
37
+
38
+ if drilled.has_key? level
39
+ # If we find something, continue drilling.
40
+ drilled = drilled[level]
41
+ levels_drilled += 1
42
+ end
43
+
44
+ end
45
+
46
+ # If we didn't drill the right number of levels, return the
47
+ # original string.
48
+ if levels.length != levels_drilled
49
+ return key
50
+ end
51
+
52
+ # Otherwise we must have drilled all they way.
53
+ return drilled
54
+ end
55
+
56
+ # Takes a site object and decides whether it is using translated builds.
57
+ def opensdg_translated_builds(site)
58
+ # Assume the site is using translated builds.
59
+ translated_builds = true
60
+ site.config['languages'].each do |language|
61
+ # If any languages don't have a key in site.data, the site is not using
62
+ # translated builds.
63
+ if !site.data.has_key? language
64
+ translated_builds = false
65
+ end
66
+ end
67
+ return translated_builds
68
+ end
69
+
70
+ # Print a notice during compilation.
71
+ def opensdg_notice(message)
72
+ Jekyll.logger.warn message.yellow
73
+ end
74
+
75
+ # Get the public language codes for a site, keyed by the actual language codes.
76
+ def opensdg_languages_public(site)
77
+ languages_public = site.config['languages_public']
78
+
79
+ # The current structure of the setting is an array of hashes, each containing
80
+ # keys for "language" and "language_public".
81
+ if languages_public.is_a?(Array)
82
+ converted_languages_public = Hash.new
83
+ languages_public.each do |language_public|
84
+ language_code = language_public['language']
85
+ language_code_public = language_public['language_public']
86
+ converted_languages_public[language_code] = language_code_public
87
+ end
88
+ return converted_languages_public
89
+ end
90
+
91
+ # Fallback to exactly what was retrieved from site.confg['languages_public'],
92
+ # since the deprecated structure is exactly what this function wants.
93
+ return languages_public
94
+ end
@@ -1,417 +1,439 @@
1
- require "jekyll"
2
- require_relative "helpers"
3
-
4
- module JekyllOpenSdgPlugins
5
- class SDGVariables < Jekyll::Generator
6
- safe true
7
- priority :low
8
-
9
- # Get a goal number from an indicator number.
10
- def get_goal_number(indicator_number)
11
- parts = indicator_number.split('.')
12
- parts[0]
13
- end
14
-
15
- # Get a target number from an indicator number.
16
- def get_target_number(indicator_number)
17
- parts = indicator_number.split('.')
18
- parts[0] + '.' + parts[1]
19
- end
20
-
21
- # Make any goal/target/indicator number suitable for use in sorting.
22
- def get_sort_order(number)
23
- if number.is_a? Numeric
24
- number = number.to_s
25
- end
26
- sort_order = ''
27
- parts = number.split('.')
28
- parts.each do |part|
29
- if part.length == 1
30
- part = '0' + part
31
- end
32
- sort_order += part
33
- end
34
- sort_order
35
- end
36
-
37
- # The Jekyll baseurl is user-configured, and can be inconsistent. This
38
- # ensure it is consistent in whether it starts/ends with a slash.
39
- def normalize_baseurl(baseurl)
40
- if baseurl == ''
41
- baseurl = '/'
42
- end
43
- if !baseurl.start_with? '/'
44
- baseurl = '/' + baseurl
45
- end
46
- if !baseurl.end_with? '/'
47
- baseurl = baseurl + '/'
48
- end
49
- baseurl
50
- end
51
-
52
- # Compute a URL for an item, given it's number.
53
- def get_url(baseurl, language, number, languages, languages_public)
54
-
55
- baseurl = normalize_baseurl(baseurl)
56
-
57
- default_language = languages[0]
58
- language_public = language
59
- if languages_public && languages_public[language]
60
- language_public = languages_public[language]
61
- end
62
- if default_language != language
63
- baseurl += language_public + '/'
64
- end
65
-
66
- number = number.gsub('.', '-')
67
- baseurl + number
68
- end
69
-
70
- # Get a Hash of all the URLs based on one particular one.
71
- def get_all_urls(url, language, languages, languages_public, baseurl)
72
-
73
- baseurl = normalize_baseurl(baseurl)
74
-
75
- language_public = language
76
- if languages_public && languages_public[language]
77
- language_public = languages_public[language]
78
- end
79
-
80
- # First figure out the language-free URL.
81
- default_language = languages[0]
82
- if language == default_language
83
- url_without_language = url
84
- else
85
- url_without_language = url.gsub('/' + language_public + '/', '/')
86
- end
87
-
88
- urls = {
89
- language => url
90
- }
91
- if language != default_language
92
- default_language_url = baseurl + url_without_language
93
- # Fix potential double-slash.
94
- default_language_url = default_language_url.gsub('//', '/')
95
- urls[default_language] = default_language_url
96
- end
97
- languages.each do |other_language|
98
- if other_language == language
99
- next
100
- end
101
- if other_language == default_language
102
- next
103
- end
104
- other_language_public = other_language
105
- if languages_public && languages_public[other_language]
106
- other_language_public = languages_public[other_language]
107
- end
108
- urls[other_language] = baseurl + other_language_public + url_without_language
109
- end
110
- urls
111
- end
112
-
113
- # Compute a URL for tha goal image, given it's number.
114
- def get_goal_image(goal_image_base, language, number)
115
- goal_image_base + '/' + language + '/' + number + '.png'
116
- end
117
-
118
- # This creates variables for use in Liquid templates under "page".
119
- # We'll create lists of goals, targets, and indicators. These will be put
120
- # on the page object. Eg: page.goals. In order to generate these lists
121
- # we will make use of the metadata. Each item in the list will be a hash
122
- # containing these keys:
123
- # - name (translated)
124
- # - number (the "id" or number, eg: 1, 1.2, 1.2.1, etc.)
125
- # - slug (version of 'number' but with dashes instead of dots)
126
- # - sort (for the purposes of sorting the items, if needed)
127
- # - global (a Hash containing any equivalent global metadata)
128
- # The goal hashes contain additional keys:
129
- # - short (the translated short version of the name)
130
- # - icon (path to the translated icon)
131
- # - url (path to the goal page)
132
- # The target hashes contain additional keys:
133
- # - goal_number (the goal number for this target)
134
- # The indicator hashes contain additional keys:
135
- # - url (path to the indicator page)
136
- # - goal_number (the goal number for this indicator)
137
- # - target_number (the target number for this indicator)
138
- # - [all metadata fields from the indicator]
139
- # The lists are:
140
- # - goals
141
- # - targets
142
- # - indicators
143
- # Additionally, on indicator pages themselves, there are variables for
144
- # the current goal/target/indicator:
145
- # - goal
146
- # - target
147
- # - indicator
148
- # Similarly, on goal pages themselves, there are variables for the current
149
- # goal:
150
- # - goal
151
- def generate(site)
152
-
153
- # Some general variables needed below.
154
- translations = site.data['translations']
155
- languages = site.config['languages']
156
- languages_public = site.config['languages_public']
157
- default_language = languages[0]
158
- baseurl = site.config['baseurl']
159
- goal_image_base = site.config['goal_image_base']
160
-
161
- # These keys are flagged as "protected" here so that we can make sure that
162
- # country-specific metadata doesn't use any of these fields.
163
- protected_keys = ['goals', 'goal', 'targets', 'target', 'indicators',
164
- 'indicator', 'language', 'name', 'number', 'sort', 'global', 'url',
165
- 'goal_number', 'target_number'
166
- ]
167
-
168
- # Figure out from our translations the global indicator numbers.
169
- global_inids = translations[default_language]['global_indicators'].keys
170
- global_inids = global_inids.select { |x| x.end_with? '-title' }
171
- global_inids = global_inids.map { |x| x.gsub('-title', '').gsub('-', '.') }
172
-
173
- # For available indicators, we simply map the "indicators" collection.
174
- available_inids = site.collections['indicators'].docs.select { |x| x.data['language'] == default_language }
175
- available_inids = available_inids.map { |x| x.data['indicator'] }
176
- available_indicators = {}
177
- available_targets = {}
178
- available_goals = {}
179
-
180
- # Some throwaway variables to keep track of what has been added.
181
- already_added = {}
182
-
183
- # Set up some empty hashes, per language.
184
- languages.each do |language|
185
- available_goals[language] = []
186
- available_targets[language] = []
187
- available_indicators[language] = []
188
- already_added[language] = []
189
- end
190
-
191
- # Populate the hashes.
192
- available_inids.each do |indicator_number|
193
- goal_number = get_goal_number(indicator_number)
194
- target_number = get_target_number(indicator_number)
195
- is_global_indicator = global_inids.index(indicator_number) != nil
196
- # To get the name of global stuff, we can use predicable translation
197
- # keys from the SDG Translations project. Eg: global_goals.1-title
198
- goal_translation_key = 'global_goals.' + goal_number
199
- target_translation_key = 'global_targets.' + target_number.gsub('.', '-')
200
- indicator_translation_key = 'global_indicators.' + indicator_number.gsub('.', '-')
201
-
202
- languages.each do |language|
203
- global_goal = {
204
- 'name' => opensdg_translate_key(goal_translation_key + '-title', translations, language),
205
- # TODO: More global metadata about goals?
206
- }
207
- global_target = {
208
- 'name' => opensdg_translate_key(target_translation_key + '-title', translations, language),
209
- # TODO: More global metadata about targets?
210
- }
211
- global_indicator = {}
212
- if is_global_indicator
213
- global_indicator = {
214
- 'name' => opensdg_translate_key(indicator_translation_key + '-title', translations, language),
215
- # TODO: More global metadata about indicators?
216
- }
217
- end
218
-
219
- # We have to get the metadata for the indicator/language.
220
- meta = {}
221
- # Currently the meta keys are dash-delimited. This is a little
222
- # arbitrary (it's because they came from filenames) and could maybe
223
- # be changed eventually to dot-delimited for consistency.
224
- meta_key = indicator_number.gsub('.', '-')
225
- # The location of the metadata is different depending on whether we are
226
- # using "translated_builds" or not.
227
- if opensdg_translated_builds(site)
228
- meta = site.data[language]['meta'][meta_key]
229
- else
230
- meta = site.data['meta'][meta_key]
231
- # Also for untranslated builds, we need to support the "subfolder"
232
- # approach for metadata translation. (This is handled at build-time
233
- # for translated builds.)
234
- if meta.has_key? language
235
- meta = meta.merge(meta[language])
236
- meta.delete(language)
237
- end
238
- end
239
-
240
- # Set the goal for this language, once only.
241
- if already_added[language].index(goal_number) == nil
242
- already_added[language].push(goal_number)
243
- available_goal = {
244
- 'number' => goal_number,
245
- 'slug' => goal_number.gsub('.', '-'),
246
- 'name' => opensdg_translate_key(goal_translation_key + '-title', translations, language),
247
- 'short' => opensdg_translate_key(goal_translation_key + '-short', translations, language),
248
- 'url' => get_url(baseurl, language, goal_number, languages, languages_public),
249
- 'icon' => get_goal_image(goal_image_base, language, goal_number),
250
- 'sort' => get_sort_order(goal_number),
251
- 'global' => global_goal,
252
- }
253
- available_goals[language].push(available_goal)
254
- end
255
- # Set the target for this language, once only.
256
- if already_added[language].index(target_number) == nil
257
- already_added[language].push(target_number)
258
- available_target = {
259
- 'number' => target_number,
260
- 'slug' => target_number.gsub('.', '-'),
261
- 'name' => opensdg_translate_key(target_translation_key + '-title', translations, language),
262
- 'sort' => get_sort_order(target_number),
263
- 'goal_number' => goal_number,
264
- 'global' => global_target,
265
- }
266
- available_targets[language].push(available_target)
267
- end
268
- # Set the indicator for this language. Unfortunately we are currently
269
- # using two possible fields for the indicator name:
270
- # - indicator_name
271
- # - indicator_name_national
272
- # TODO: Eventually standardize around 'indicator_name' and drop support
273
- # for 'indicator_name_national'.
274
- indicator_name = ''
275
- if meta.has_key? 'indicator_name_national'
276
- indicator_name = meta['indicator_name_national']
277
- else
278
- indicator_name = meta['indicator_name']
279
- end
280
- available_indicator = {
281
- 'number' => indicator_number,
282
- 'slug' => indicator_number.gsub('.', '-'),
283
- 'name' => opensdg_translate_key(indicator_name, translations, language),
284
- 'url' => get_url(baseurl, language, indicator_number, languages, languages_public),
285
- 'sort' => get_sort_order(indicator_number),
286
- 'goal_number' => goal_number,
287
- 'target_number' => target_number,
288
- 'global' => global_indicator,
289
- }
290
- # Translate and add any metadata.
291
- meta.each do |key, value|
292
- if !protected_keys.include? key
293
- available_indicator[key] = opensdg_translate_key(value, translations, language)
294
- end
295
- end
296
- available_indicators[language].push(available_indicator)
297
- end
298
- end
299
-
300
- # Sort all the items.
301
- languages.each do |lang|
302
- available_goals[lang] = available_goals[lang].sort_by { |x| x['sort'] }
303
- available_targets[lang] = available_targets[lang].sort_by { |x| x['sort'] }
304
- available_indicators[lang] = available_indicators[lang].sort_by { |x| x['sort'] }
305
- end
306
-
307
- # Next set the stuff on each doc in certain collections, according
308
- # to the doc's language. We'll be putting the global stuff on every
309
- # page, goal, and indicator across the site. This may be a bit memory-
310
- # intensive during the Jekyll build, but it is nice to have it available
311
- # for consistency.
312
- site.collections.keys.each do |collection|
313
- site.collections[collection].docs.each do |doc|
314
- # Ensure it has a language.
315
- if !doc.data.has_key? 'language'
316
- doc.data['language'] = default_language
317
- end
318
- language = doc.data['language']
319
- # Set these on the page object.
320
- doc.data['goals'] = available_goals[language]
321
- doc.data['targets'] = available_targets[language]
322
- doc.data['indicators'] = available_indicators[language]
323
- doc.data['baseurl'] = get_url(baseurl, language, '', languages, languages_public)
324
- doc.data['url_by_language'] = get_all_urls(doc.url, language, languages, languages_public, baseurl)
325
- doc.data['t'] = site.data['translations'][language]
326
-
327
- # Set the remote_data_prefix for this page.
328
- if site.config.has_key? 'remote_data_prefix'
329
- doc.data['remote_data_prefix'] = site.config['remote_data_prefix']
330
- elsif site.config.has_key? 'local_data_folder'
331
- doc.data['remote_data_prefix'] = normalize_baseurl(baseurl)
332
- end
333
- if opensdg_translated_builds(site)
334
- doc.data['remote_data_prefix'] = File.join(doc.data['remote_data_prefix'], language)
335
- end
336
-
337
- if collection == 'indicators'
338
- # For indicators we also set the current indicator/target/goal.
339
- if doc.data.has_key? 'indicator_number'
340
- indicator_number = doc.data['indicator_number']
341
- elsif doc.data.has_key? 'indicator'
342
- # Backwards compatibility.
343
- indicator_number = doc.data['indicator']
344
- else
345
- raise "Error: An indicator does not have 'indicator_number' property."
346
- end
347
- # Force the indicator number to be a string.
348
- if indicator_number.is_a? Numeric
349
- indicator_number = indicator_number.to_s
350
- end
351
- goal_number = get_goal_number(indicator_number)
352
- target_number = get_target_number(indicator_number)
353
- doc.data['goal'] = available_goals[language].find {|x| x['number'] == goal_number}
354
- doc.data['target'] = available_targets[language].find {|x| x['number'] == target_number}
355
- doc.data['indicator'] = available_indicators[language].find {|x| x['number'] == indicator_number}
356
- elsif collection == 'goals'
357
- # For goals we also set the current goal.
358
- if doc.data.has_key? 'goal_number'
359
- goal_number = doc.data['goal_number']
360
- elsif doc.data.has_key? 'sdg_goal'
361
- # Backwards compatibility.
362
- goal_number = doc.data['sdg_goal']
363
- else
364
- raise "Error: A goal does not have 'goal_number' property."
365
- end
366
- # Force the goal number to be a string.
367
- if goal_number.is_a? Numeric
368
- goal_number = goal_number.to_s
369
- end
370
- doc.data['goal'] = available_goals[language].find {|x| x['number'] == goal_number}
371
- end
372
- end
373
- end
374
-
375
- # Finally let's set all these on the site object so that they can be
376
- # easily looked up later.
377
- lookup = {}
378
- available_goals.each do |language, items|
379
- lookup[language] = {}
380
- items.each do |item|
381
- number = item['number']
382
- lookup[language][number] = item
383
- end
384
- end
385
- available_targets.each do |language, items|
386
- items.each do |item|
387
- number = item['number']
388
- lookup[language][number] = item
389
- end
390
- end
391
- available_indicators.each do |language, items|
392
- items.each do |item|
393
- number = item['number']
394
- lookup[language][number] = item
395
- end
396
- end
397
- site.data['sdg_lookup'] = lookup
398
-
399
- end
400
- end
401
- end
402
-
403
- module Jekyll
404
- module SDGLookup
405
- # This provides a "sdg_lookup" filter that takes an id and returns a hash
406
- # representation of a goal, target, or indicator.
407
- def sdg_lookup(number)
408
- number = number.gsub('-', '.')
409
- data = @context.registers[:site].data
410
- page = @context.environments.first['page']
411
- language = page['language']
412
- return data['sdg_lookup'][language][number]
413
- end
414
- end
415
- end
416
-
417
- Liquid::Template.register_filter(Jekyll::SDGLookup)
1
+ require "jekyll"
2
+ require_relative "helpers"
3
+
4
+ module JekyllOpenSdgPlugins
5
+ class SDGVariables < Jekyll::Generator
6
+ safe true
7
+ priority :low
8
+
9
+ # Get a goal number from an indicator number.
10
+ def get_goal_number(indicator_number)
11
+ parts = indicator_number.split('.')
12
+ parts[0]
13
+ end
14
+
15
+ # Get a target number from an indicator number.
16
+ def get_target_number(indicator_number)
17
+ parts = indicator_number.split('.')
18
+ parts[0] + '.' + parts[1]
19
+ end
20
+
21
+ # Is this string numeric?
22
+ def is_number? string
23
+ true if Float(string) rescue false
24
+ end
25
+
26
+ # Make any goal/target/indicator number suitable for use in sorting.
27
+ def get_sort_order(number)
28
+ if number.is_a? Numeric
29
+ number = number.to_s
30
+ end
31
+ sort_order = ''
32
+ parts = number.split('.')
33
+ parts.each do |part|
34
+ if part.length == 1
35
+ if is_number?(part)
36
+ part = '0' + part
37
+ else
38
+ part = part + part
39
+ end
40
+ end
41
+ sort_order += part
42
+ end
43
+ sort_order
44
+ end
45
+
46
+ # The Jekyll baseurl is user-configured, and can be inconsistent. This
47
+ # ensure it is consistent in whether it starts/ends with a slash.
48
+ def normalize_baseurl(baseurl)
49
+ if baseurl == ''
50
+ baseurl = '/'
51
+ end
52
+ if !baseurl.start_with? '/'
53
+ baseurl = '/' + baseurl
54
+ end
55
+ if !baseurl.end_with? '/'
56
+ baseurl = baseurl + '/'
57
+ end
58
+ baseurl
59
+ end
60
+
61
+ # Compute a URL for an item, given it's number.
62
+ def get_url(baseurl, language, number, languages, languages_public)
63
+
64
+ baseurl = normalize_baseurl(baseurl)
65
+
66
+ default_language = languages[0]
67
+ language_public = language
68
+ if languages_public && languages_public[language]
69
+ language_public = languages_public[language]
70
+ end
71
+ if default_language != language
72
+ baseurl += language_public + '/'
73
+ end
74
+
75
+ number = number.gsub('.', '-')
76
+ baseurl + number
77
+ end
78
+
79
+ # Get a Hash of all the URLs based on one particular one.
80
+ def get_all_urls(url, language, languages, languages_public, baseurl)
81
+
82
+ baseurl = normalize_baseurl(baseurl)
83
+
84
+ language_public = language
85
+ if languages_public && languages_public[language]
86
+ language_public = languages_public[language]
87
+ end
88
+
89
+ # First figure out the language-free URL.
90
+ default_language = languages[0]
91
+ if language == default_language
92
+ url_without_language = url
93
+ else
94
+ url_without_language = url.gsub('/' + language_public + '/', '/')
95
+ end
96
+
97
+ urls = {
98
+ language => url
99
+ }
100
+ if language != default_language
101
+ default_language_url = baseurl + url_without_language
102
+ # Fix potential double-slash.
103
+ default_language_url = default_language_url.gsub('//', '/')
104
+ urls[default_language] = default_language_url
105
+ end
106
+ languages.each do |other_language|
107
+ if other_language == language
108
+ next
109
+ end
110
+ if other_language == default_language
111
+ next
112
+ end
113
+ other_language_public = other_language
114
+ if languages_public && languages_public[other_language]
115
+ other_language_public = languages_public[other_language]
116
+ end
117
+ urls[other_language] = baseurl + other_language_public + url_without_language
118
+ end
119
+ urls
120
+ end
121
+
122
+ # Compute a URL for tha goal image, given it's number.
123
+ def get_goal_image(goal_image_base, language, number, extension)
124
+ goal_image_base + '/' + language + '/' + number + '.' + extension
125
+ end
126
+
127
+ # This creates variables for use in Liquid templates under "page".
128
+ # We'll create lists of goals, targets, and indicators. These will be put
129
+ # on the page object. Eg: page.goals. In order to generate these lists
130
+ # we will make use of the metadata. Each item in the list will be a hash
131
+ # containing these keys:
132
+ # - name (translated)
133
+ # - number (the "id" or number, eg: 1, 1.2, 1.2.1, etc.)
134
+ # - slug (version of 'number' but with dashes instead of dots)
135
+ # - sort (for the purposes of sorting the items, if needed)
136
+ # - global (a Hash containing any equivalent global metadata)
137
+ # The goal hashes contain additional keys:
138
+ # - short (the translated short version of the name)
139
+ # - icon (path to the translated icon)
140
+ # - url (path to the goal page)
141
+ # The target hashes contain additional keys:
142
+ # - goal_number (the goal number for this target)
143
+ # The indicator hashes contain additional keys:
144
+ # - url (path to the indicator page)
145
+ # - goal_number (the goal number for this indicator)
146
+ # - target_number (the target number for this indicator)
147
+ # - [all metadata fields from the indicator]
148
+ # The lists are:
149
+ # - goals
150
+ # - targets
151
+ # - indicators
152
+ # Additionally, on indicator pages themselves, there are variables for
153
+ # the current goal/target/indicator:
154
+ # - goal
155
+ # - target
156
+ # - indicator
157
+ # Similarly, on goal pages themselves, there are variables for the current
158
+ # goal:
159
+ # - goal
160
+ def generate(site)
161
+
162
+ # Some general variables needed below.
163
+ translations = site.data['translations']
164
+ languages = site.config['languages']
165
+ languages_public = opensdg_languages_public(site)
166
+ default_language = languages[0]
167
+ baseurl = site.config['baseurl']
168
+ goal_image_base = 'https://open-sdg.org/sdg-translations/assets/img/goals'
169
+ if site.config.has_key? 'goal_image_base'
170
+ goal_image_base = site.config['goal_image_base']
171
+ end
172
+ goal_image_extension = 'png'
173
+ if site.config.has_key? 'goal_image_extension'
174
+ goal_image_extension = site.config['goal_image_extension']
175
+ end
176
+
177
+ # These keys are flagged as "protected" here so that we can make sure that
178
+ # country-specific metadata doesn't use any of these fields.
179
+ protected_keys = ['goals', 'goal', 'targets', 'target', 'indicators',
180
+ 'indicator', 'language', 'name', 'number', 'sort', 'global', 'url',
181
+ 'goal_number', 'target_number'
182
+ ]
183
+
184
+ # Figure out from our translations the global indicator numbers.
185
+ global_inids = translations[default_language]['global_indicators'].keys
186
+ global_inids = global_inids.select { |x| x.end_with? '-title' }
187
+ global_inids = global_inids.map { |x| x.gsub('-title', '').gsub('-', '.') }
188
+
189
+ # For available indicators, we simply map the "indicators" collection.
190
+ available_inids = site.collections['indicators'].docs.select { |x| x.data['language'] == default_language }
191
+ available_inids = available_inids.map { |x| x.data['indicator'] }
192
+ available_indicators = {}
193
+ available_targets = {}
194
+ available_goals = {}
195
+
196
+ # Some throwaway variables to keep track of what has been added.
197
+ already_added = {}
198
+
199
+ # Set up some empty hashes, per language.
200
+ languages.each do |language|
201
+ available_goals[language] = []
202
+ available_targets[language] = []
203
+ available_indicators[language] = []
204
+ already_added[language] = []
205
+ end
206
+
207
+ # Populate the hashes.
208
+ available_inids.each do |indicator_number|
209
+ goal_number = get_goal_number(indicator_number)
210
+ target_number = get_target_number(indicator_number)
211
+ is_global_indicator = global_inids.index(indicator_number) != nil
212
+ # To get the name of global stuff, we can use predicable translation
213
+ # keys from the SDG Translations project. Eg: global_goals.1-title
214
+ goal_translation_key = 'global_goals.' + goal_number
215
+ target_translation_key = 'global_targets.' + target_number.gsub('.', '-')
216
+ indicator_translation_key = 'global_indicators.' + indicator_number.gsub('.', '-')
217
+
218
+ languages.each do |language|
219
+ global_goal = {
220
+ 'name' => opensdg_translate_key(goal_translation_key + '-title', translations, language),
221
+ # TODO: More global metadata about goals?
222
+ }
223
+ global_target = {
224
+ 'name' => opensdg_translate_key(target_translation_key + '-title', translations, language),
225
+ # TODO: More global metadata about targets?
226
+ }
227
+ global_indicator = {}
228
+ if is_global_indicator
229
+ global_indicator = {
230
+ 'name' => opensdg_translate_key(indicator_translation_key + '-title', translations, language),
231
+ # TODO: More global metadata about indicators?
232
+ }
233
+ end
234
+
235
+ # We have to get the metadata for the indicator/language.
236
+ meta = {}
237
+ # Currently the meta keys are dash-delimited. This is a little
238
+ # arbitrary (it's because they came from filenames) and could maybe
239
+ # be changed eventually to dot-delimited for consistency.
240
+ meta_key = indicator_number.gsub('.', '-')
241
+ # The location of the metadata is different depending on whether we are
242
+ # using "translated_builds" or not.
243
+ if opensdg_translated_builds(site)
244
+ meta = site.data[language]['meta'][meta_key]
245
+ else
246
+ meta = site.data['meta'][meta_key]
247
+ # Also for untranslated builds, we need to support the "subfolder"
248
+ # approach for metadata translation. (This is handled at build-time
249
+ # for translated builds.)
250
+ if meta.has_key? language
251
+ meta = meta.merge(meta[language])
252
+ meta.delete(language)
253
+ end
254
+ end
255
+
256
+ # Set the goal for this language, once only.
257
+ if already_added[language].index(goal_number) == nil
258
+ already_added[language].push(goal_number)
259
+ available_goal = {
260
+ 'number' => goal_number,
261
+ 'slug' => goal_number.gsub('.', '-'),
262
+ 'name' => opensdg_translate_key(goal_translation_key + '-title', translations, language),
263
+ 'short' => opensdg_translate_key(goal_translation_key + '-short', translations, language),
264
+ 'url' => get_url(baseurl, language, goal_number, languages, languages_public),
265
+ 'icon' => get_goal_image(goal_image_base, language, goal_number, goal_image_extension),
266
+ 'sort' => get_sort_order(goal_number),
267
+ 'global' => global_goal,
268
+ }
269
+ available_goals[language].push(available_goal)
270
+ end
271
+ # Set the target for this language, once only.
272
+ if already_added[language].index(target_number) == nil
273
+ already_added[language].push(target_number)
274
+ available_target = {
275
+ 'number' => target_number,
276
+ 'slug' => target_number.gsub('.', '-'),
277
+ 'name' => opensdg_translate_key(target_translation_key + '-title', translations, language),
278
+ 'sort' => get_sort_order(target_number),
279
+ 'goal_number' => goal_number,
280
+ 'global' => global_target,
281
+ }
282
+ available_targets[language].push(available_target)
283
+ end
284
+ # Set the indicator for this language. Unfortunately we are currently
285
+ # using two possible fields for the indicator name:
286
+ # - indicator_name
287
+ # - indicator_name_national
288
+ # TODO: Eventually standardize around 'indicator_name' and drop support
289
+ # for 'indicator_name_national'.
290
+ indicator_name = ''
291
+ if meta.has_key? 'indicator_name_national'
292
+ indicator_name = meta['indicator_name_national']
293
+ else
294
+ indicator_name = meta['indicator_name']
295
+ end
296
+ available_indicator = {
297
+ 'number' => indicator_number,
298
+ 'slug' => indicator_number.gsub('.', '-'),
299
+ 'name' => opensdg_translate_key(indicator_name, translations, language),
300
+ 'url' => get_url(baseurl, language, indicator_number, languages, languages_public),
301
+ 'sort' => get_sort_order(indicator_number),
302
+ 'goal_number' => goal_number,
303
+ 'target_number' => target_number,
304
+ 'global' => global_indicator,
305
+ }
306
+ # Translate and add any metadata.
307
+ meta.each do |key, value|
308
+ if !protected_keys.include? key
309
+ available_indicator[key] = opensdg_translate_key(value, translations, language)
310
+ end
311
+ end
312
+ available_indicators[language].push(available_indicator)
313
+ end
314
+ end
315
+
316
+ # Sort all the items.
317
+ languages.each do |lang|
318
+ available_goals[lang] = available_goals[lang].sort_by { |x| x['sort'] }
319
+ available_targets[lang] = available_targets[lang].sort_by { |x| x['sort'] }
320
+ available_indicators[lang] = available_indicators[lang].sort_by { |x| x['sort'] }
321
+ end
322
+
323
+ # Next set the stuff on each doc in certain collections, according
324
+ # to the doc's language. We'll be putting the global stuff on every
325
+ # page, goal, and indicator across the site. This may be a bit memory-
326
+ # intensive during the Jekyll build, but it is nice to have it available
327
+ # for consistency.
328
+ site.collections.keys.each do |collection|
329
+ site.collections[collection].docs.each do |doc|
330
+ # Ensure it has a language.
331
+ if !doc.data.has_key? 'language'
332
+ doc.data['language'] = default_language
333
+ end
334
+ # Ensure it has a valid language.
335
+ if !languages.include? doc.data['language']
336
+ message = "NOTICE: The document '#{doc.basename}' has an unexpected language '#{doc.data['language']}' so we are using the default language '#{default_language}' instead."
337
+ opensdg_notice(message)
338
+ doc.data['language'] = default_language
339
+ end
340
+ language = doc.data['language']
341
+ # Set these on the page object.
342
+ doc.data['goals'] = available_goals[language]
343
+ doc.data['targets'] = available_targets[language]
344
+ doc.data['indicators'] = available_indicators[language]
345
+ doc.data['baseurl'] = get_url(baseurl, language, '', languages, languages_public)
346
+ doc.data['url_by_language'] = get_all_urls(doc.url, language, languages, languages_public, baseurl)
347
+ doc.data['t'] = site.data['translations'][language]
348
+
349
+ # Set the remote_data_prefix for this page.
350
+ if site.config.has_key? 'remote_data_prefix'
351
+ doc.data['remote_data_prefix'] = site.config['remote_data_prefix']
352
+ elsif site.config.has_key? 'local_data_folder'
353
+ doc.data['remote_data_prefix'] = normalize_baseurl(baseurl)
354
+ end
355
+ if opensdg_translated_builds(site)
356
+ doc.data['remote_data_prefix'] = File.join(doc.data['remote_data_prefix'], language)
357
+ end
358
+
359
+ if collection == 'indicators'
360
+ # For indicators we also set the current indicator/target/goal.
361
+ if doc.data.has_key? 'indicator_number'
362
+ indicator_number = doc.data['indicator_number']
363
+ elsif doc.data.has_key? 'indicator'
364
+ # Backwards compatibility.
365
+ indicator_number = doc.data['indicator']
366
+ else
367
+ raise "Error: An indicator does not have 'indicator_number' property."
368
+ end
369
+ # Force the indicator number to be a string.
370
+ if indicator_number.is_a? Numeric
371
+ indicator_number = indicator_number.to_s
372
+ end
373
+ goal_number = get_goal_number(indicator_number)
374
+ target_number = get_target_number(indicator_number)
375
+ doc.data['goal'] = available_goals[language].find {|x| x['number'] == goal_number}
376
+ doc.data['target'] = available_targets[language].find {|x| x['number'] == target_number}
377
+ doc.data['indicator'] = available_indicators[language].find {|x| x['number'] == indicator_number}
378
+ elsif collection == 'goals'
379
+ # For goals we also set the current goal.
380
+ if doc.data.has_key? 'goal_number'
381
+ goal_number = doc.data['goal_number']
382
+ elsif doc.data.has_key? 'sdg_goal'
383
+ # Backwards compatibility.
384
+ goal_number = doc.data['sdg_goal']
385
+ else
386
+ raise "Error: A goal does not have 'goal_number' property."
387
+ end
388
+ # Force the goal number to be a string.
389
+ if goal_number.is_a? Numeric
390
+ goal_number = goal_number.to_s
391
+ end
392
+ doc.data['goal'] = available_goals[language].find {|x| x['number'] == goal_number}
393
+ end
394
+ end
395
+ end
396
+
397
+ # Finally let's set all these on the site object so that they can be
398
+ # easily looked up later.
399
+ lookup = {}
400
+ available_goals.each do |language, items|
401
+ lookup[language] = {}
402
+ items.each do |item|
403
+ number = item['number']
404
+ lookup[language][number] = item
405
+ end
406
+ end
407
+ available_targets.each do |language, items|
408
+ items.each do |item|
409
+ number = item['number']
410
+ lookup[language][number] = item
411
+ end
412
+ end
413
+ available_indicators.each do |language, items|
414
+ items.each do |item|
415
+ number = item['number']
416
+ lookup[language][number] = item
417
+ end
418
+ end
419
+ site.data['sdg_lookup'] = lookup
420
+
421
+ end
422
+ end
423
+ end
424
+
425
+ module Jekyll
426
+ module SDGLookup
427
+ # This provides a "sdg_lookup" filter that takes an id and returns a hash
428
+ # representation of a goal, target, or indicator.
429
+ def sdg_lookup(number)
430
+ number = number.gsub('-', '.')
431
+ data = @context.registers[:site].data
432
+ page = @context.environments.first['page']
433
+ language = page['language']
434
+ return data['sdg_lookup'][language][number]
435
+ end
436
+ end
437
+ end
438
+
439
+ Liquid::Template.register_filter(Jekyll::SDGLookup)