jekyll-open-sdg-plugins 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)