• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-country / 15022004526

14 May 2025 01:29PM UTC coverage: 42.791% (-0.2%) from 43.025%
15022004526

Pull #328

github

web-flow
Merge f0e471a76 into 17e9fd3be
Pull Request #328: Load required "setxkbmap" package on demand (bsc#1243088)

1484 of 3468 relevant lines covered (42.79%)

12.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.98
/language/src/modules/Language.rb
1
# encoding: utf-8
2

3
# ------------------------------------------------------------------------------
4
# Copyright (c) 2012 Novell, Inc. All Rights Reserved.
5
#
6
#
7
# This program is free software; you can redistribute it and/or modify it under
8
# the terms of version 2 of the GNU General Public License as published by the
9
# Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along with
16
# this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail, you may find
19
# current contact information at www.novell.com.
20
# ------------------------------------------------------------------------------
21

22
# File:        modules/Language.ycp
23
# Module:        Language
24
# Summary:        This module does all language related stuff:
25
# Authors:        Klaus Kaempf <kkaempf@suse.de>
26
#                Thomas Roelz <tom@suse.de>
27
# Maintainer:  Jiri Suchomel <jsuchome@suse.cz>
28
#
29
# $Id$
30
require "yast"
1✔
31
require "yast2/execute"
1✔
32

33
require "shellwords"
1✔
34

35
module Yast
1✔
36
  class LanguageClass < Module
1✔
37
    DEFAULT_FALLBACK_LANGUAGE = "en_US".freeze
1✔
38

39
    include Yast::Logger
1✔
40

41
    require "y2country/language_dbus"
1✔
42

43
    def main
1✔
44
      Yast.import "Pkg"
69✔
45
      Yast.import "UI"
69✔
46
      textdomain "country"
69✔
47

48
      Yast.import "AsciiFile"
69✔
49
      Yast.import "Directory"
69✔
50
      Yast.import "Encoding"
69✔
51
      Yast.import "FileUtils"
69✔
52
      Yast.import "InstExtensionImage"
69✔
53
      Yast.import "Linuxrc"
69✔
54
      Yast.import "Misc"
69✔
55
      Yast.import "Mode"
69✔
56
      Yast.import "PackageCallbacks"
69✔
57
      Yast.import "PackageSystem"
69✔
58
      Yast.import "Popup"
69✔
59
      Yast.import "ProductFeatures"
69✔
60
      Yast.import "Report"
69✔
61
      Yast.import "SlideShow"
69✔
62
      Yast.import "Stage"
69✔
63

64
      # directory where all the language definitions are stored
65
      # it's a constant, but depends on a dynamic content (Directory)
66
      @languages_directory = "#{Directory.datadir}/languages"
69✔
67

68
      # currently selected language
69
      @language = DEFAULT_FALLBACK_LANGUAGE
69✔
70

71
      # original language
72
      @language_on_entry = DEFAULT_FALLBACK_LANGUAGE
69✔
73

74
      # language preselected in /etc/install.inf
75
      @preselected = DEFAULT_FALLBACK_LANGUAGE
69✔
76

77
      # user readable description of language
78
      @name = "English (US)"
69✔
79

80
      @linuxrc_language_set = false
69✔
81

82
      # Default language to be restored with MakeProposal.
83
      @default_language = DEFAULT_FALLBACK_LANGUAGE
69✔
84

85

86
      # Default settings for INSTALLED_LANGUAGES in /etc/sysconfig/language
87
      @languages = ""
69✔
88

89
      # Original value of INSTALLED_LANGUAGES
90
      @languages_on_entry = ""
69✔
91

92
      # Use utf8 in locale
93
      @use_utf8 = true
69✔
94

95
      # ncurses mode
96
      @text_mode = nil
69✔
97

98
      @ExpertSettingsChanged = false
69✔
99

100
      # Was the initial language selection skipped? (see bug 223258)
101
      # (It can be, if the language was selected in linuxrc)
102
      @selection_skipped = false
69✔
103

104
      # level of translation completeness
105
      @translation_status = {}
69✔
106

107
      # map (locale: 1) of available locales
108
      @locales = {}
69✔
109

110
      # map with all languages (cached - needs to be reread for retranslation)
111
      @languages_map = {}
69✔
112

113
      # mapping of language to its default (proposed) time zone
114
      @lang2timezone = {}
69✔
115

116
      # mapping of language to its default (proposed) kbd layout
117
      @lang2keyboard = {}
69✔
118

119
      # setting read from localed
120
      @localed_conf = {}
69✔
121

122
      # languages that cannot be correctly shown in text mode
123
      # if the system (Linuxrc) does not start with them from the beginning
124
      @cjk_languages = [
125
        "ja",
69✔
126
        "ko",
127
        "zh",
128
        "hi",
129
        "km",
130
        "pa",
131
        "bn",
132
        "gu",
133
        "mr",
134
        "si",
135
        "ta",
136
        "vi"
137
      ]
138

139
      # FATE #302955: Split translations out of installation system
140
      # [ "en_US", "en_GB", "de", "cs" ]
141
      @available_lang_filenames = nil
69✔
142

143
      # list of items for secondary languages term
144
      @secondary_items = []
69✔
145

146
      @english_names = {}
69✔
147

148
      @reset_recommended = true
69✔
149

150
      # language is read-only
151
      @readonly = nil
69✔
152

153
      Language()
69✔
154
    end
155

156
    #remove the suffix, if there's any (en_US.UTF-8 -> en_US)
157
    def RemoveSuffix(lang)
1✔
158
      return lang[/[a-zA-Z_]+/]
11✔
159
    end
160

161
    # Check if the language is "CJK"
162
    # (and thus could not be shown in text mode - see bug #102958)
163
    def CJKLanguage(lang)
1✔
164
      l = Builtins.substring(lang, 0, 2)
13✔
165
      Builtins.contains(@cjk_languages, l)
13✔
166
    end
167

168
    # return the value of text_mode (true for ncurses)
169
    def GetTextMode
1✔
170
      if @text_mode == nil
18✔
171
        display_info = UI.GetDisplayInfo
3✔
172
        @text_mode = Ops.get_boolean(display_info, "TextMode", false)
3✔
173
      end
174
      @text_mode
18✔
175
    end
176

177
    # Read language DB: translatable strings will be translated to current language
178
    def read_languages_map
1✔
179
      log.info("Reading languages map; @language: #{@language}")
24✔
180
      Builtins.foreach(
24✔
181
        Convert.convert(
182
          SCR.Read(path(".target.dir"), @languages_directory, []),
183
          :from => "any",
184
          :to   => "list <string>"
185
        )
186
      ) do |file|
187
        next if !Builtins.regexpmatch(file, "language_.+\\.ycp$")
1,440✔
188
        language_map = Convert.to_map(
1,416✔
189
          Builtins.eval(
190
            SCR.Read(path(".target.yast2"), Ops.add("languages/", file))
191
          )
192
        )
193
        language_map = {} if language_map == nil
1,416✔
194
        code = file
1,416✔
195
        Builtins.foreach(
1,416✔
196
          Convert.convert(
197
            language_map,
198
            :from => "map",
199
            :to   => "map <string, any>"
200
          )
201
        ) do |key, val|
202
          if Ops.is_list?(val)
4,248✔
203
            Ops.set(@languages_map, key, Convert.to_list(val))
1,416✔
204
            code = key
1,416✔
205
          end
206
        end
207
        if !Builtins.haskey(@lang2timezone, code)
1,416✔
208
          Ops.set(
1,357✔
209
            @lang2timezone,
210
            code,
211
            Ops.get_string(language_map, "timezone", "US/Eastern")
212
          )
213
        end
214
        if !Builtins.haskey(@lang2keyboard, code)
1,416✔
215
          Ops.set(
1,357✔
216
            @lang2keyboard,
217
            code,
218
            Ops.get_string(language_map, "keyboard", DEFAULT_FALLBACK_LANGUAGE)
219
          )
220
        end
221
      end
222

223
      @languages_map = {} if @languages_map == nil
24✔
224

225
      nil
24✔
226
    end
227

228
    # Read only the map of one language
229
    # @param language code
230
    def ReadLanguageMap(lang)
1✔
231
      ret = {}
×
232

233
      file = Builtins.sformat("language_%1.ycp", lang)
×
234
      if FileUtils.Exists(Ops.add(Ops.add(@languages_directory, "/"), file))
×
235
        ret = Convert.to_map(
×
236
          Builtins.eval(
237
            SCR.Read(path(".target.yast2"), Ops.add("languages/", file))
238
          )
239
        )
240
        ret = {} if ret == nil
×
241
      end
242
      deep_copy(ret)
×
243
    end
244

245
    # Return the whole map with language descriptions
246
    # @param [Boolean] force force new loading of the map from the files (forces the change
247
    # of translations to current language)
248
    def GetLanguagesMap(force)
1✔
249
      read_languages_map if Builtins.size(@languages_map) == 0 || force
×
250
      deep_copy(@languages_map)
×
251
    end
252

253
    # Return English translation of given language (Fate 301789)
254
    def EnglishName(code, backup)
1✔
255
      if Ops.get_string(@english_names, code, "") == ""
2✔
256
        if @language == DEFAULT_FALLBACK_LANGUAGE
1✔
257
          Ops.set(@english_names, code, backup)
1✔
258
        else
259
          Builtins.y2warning("nothing in english_names...")
×
260
        end
261
      end
262
      Ops.get_string(@english_names, code, backup)
2✔
263
    end
264

265
    # Fill the map with English names of languages
266
    def FillEnglishNames
1✔
267
      old_lang = WFM.GetLanguage()
1✔
268
      if @use_utf8
1✔
269
        WFM.SetLanguage(DEFAULT_FALLBACK_LANGUAGE, "UTF-8")
1✔
270
      else
271
        WFM.SetLanguage(DEFAULT_FALLBACK_LANGUAGE)
×
272
      end
273
      Builtins.foreach(GetLanguagesMap(true)) do |code, info|
1✔
274
        Ops.set(@english_names, code, Ops.get_string(info, 4, ""))
4✔
275
      end
276
      if @use_utf8
1✔
277
        WFM.SetLanguage(old_lang, "UTF-8")
1✔
278
      else
279
        WFM.SetLanguage(old_lang)
×
280
      end
281

282
      nil
1✔
283
    end
284

285

286
    # return the content of lang2timezone map
287
    # (mapping of languages to their default (proposed) time zones)
288
    def GetLang2TimezoneMap(force)
1✔
289
      read_languages_map if Builtins.size(@languages_map) == 0 && force
1✔
290
      deep_copy(@lang2timezone)
1✔
291
    end
292

293
    # return the content of lang2keyboard map
294
    # (mapping of languages to their default (proposed) keyboard layouts)
295
    def GetLang2KeyboardMap(force)
1✔
296
      read_languages_map if Builtins.size(@languages_map) == 0 && force
2✔
297
      deep_copy(@lang2keyboard)
2✔
298
    end
299

300
    # return the map of all supported countries and language codes
301
    def GetLocales
1✔
302
      if @locales == nil || @locales == {}
21✔
303
        out = SCR.Execute(path(".target.bash_output"), "/usr/bin/locale -a")
21✔
304
        Builtins.foreach(
21✔
305
          Builtins.splitstring(Ops.get_string(out, "stdout", ""), "\n")
306
        ) do |l|
307
          pos = Builtins.findfirstof(l, ".@")
357✔
308
          if pos != nil && Ops.greater_or_equal(pos, 0)
357✔
309
            l = Builtins.substring(l, 0, pos)
210✔
310
          end
311
          Ops.set(@locales, l, 1) if l != ""
357✔
312
        end
313
      end
314

315
      deep_copy(@locales)
21✔
316
    end
317

318
    # For given language, return the file name of language extension (image)
319
    # to be downloaded to the inst-sys
320
    # (FATE #302955: Split translations out of installation system)
321
    def GetLanguageExtensionFilename(language)
1✔
322
      if @available_lang_filenames == nil
4✔
323
        lang_numbers = {}
4✔
324
        Builtins.foreach(GetLanguagesMap(false)) do |code, data|
4✔
325
          short = main_language(code)
16✔
326
          if Ops.get(lang_numbers, short, 0) == 0
16✔
327
            Ops.set(lang_numbers, short, 1)
12✔
328
          else
329
            Ops.set(
4✔
330
              lang_numbers,
331
              short,
332
              Ops.add(Ops.get(lang_numbers, short, 0), 1)
333
            )
334
          end
335
        end
336
        @available_lang_filenames = Builtins.maplist(GetLanguagesMap(false)) do |code, data|
4✔
337
          short = main_language(code)
16✔
338
          if Ops.greater_than(Ops.get(lang_numbers, short, 0), 1)
16✔
339
            next code
8✔
340
          else
341
            next short
8✔
342
          end
343
        end
344
      end
345

346
      check_for_languages = [language]
4✔
347

348
      # 'en_US' ? add also 'en'
349
      check_for_languages << main_language(language) if language.include?("_")
4✔
350

351
      # Default fallback
352
      filename = "yast2-trans-en_US.rpm"
4✔
353

354
      Builtins.foreach(check_for_languages) do |one_language|
4✔
355
        if Builtins.contains(@available_lang_filenames, one_language)
7✔
356
          filename = Builtins.sformat("yast2-trans-%1.rpm", one_language)
2✔
357
          raise Break
2✔
358
        end
359
      end
360

361
      Builtins.y2milestone("Using %1 for %2", filename, language)
4✔
362
      filename
4✔
363
    end
364

365
    # Downloads inst-sys extension for a given language
366
    # including giving a UI feedback to the user
367
    #
368
    # @param [String] language, e.g. 'de_DE'
369
    def integrate_inst_sys_extension(language)
1✔
370
      log.info "integrating translation extension..."
4✔
371

372
      # busy message
373
      Popup.ShowFeedback(
4✔
374
        "",
375
        _("Downloading installation system language extension...")
376
      )
377

378
      InstExtensionImage.DownloadAndIntegrateExtension(
4✔
379
        GetLanguageExtensionFilename(language)
380
      )
381

382
      Popup.ClearFeedback
4✔
383
      log.info "integrating translation extension... done"
4✔
384
    end
385

386
    # Returns whether the given language string is supported by this library.
387
    #
388
    # @param [String] language
389
    # @see @languages_directory
390
    def valid_language?(language)
1✔
391
      GetLanguagesMap(false).key?(language)
10✔
392
    end
393

394
    # Checks whether given language is supported by the installer
395
    # and changes it to the default language en_US if it isn't.
396
    #
397
    # @param language [String] reference to the new language
398
    # @param error_report [Boolean] showing an error popup
399
    # @return [String] new (corrected) language
400
    def correct_language(language, error_report: true)
1✔
401
      # No correction needed, this is already a correct language definition
402
      return language if valid_language?(language)
21✔
403

404
      # TRANSLATORS: Error message. Strings marked %{...} will be replaced
405
      # with variable content - do not translate them, please.
406
      Report.Error(
407
        _("Language '%{language}' was not found within the list of supported languages\n" +
408
          "available at %{directory}.\n\nFallback language %{fallback} will be used."
409
        ) % {
410
          :language => language,
411
          :directory => @languages_directory,
412
          :fallback => DEFAULT_FALLBACK_LANGUAGE
413
        }
414
      ) if error_report
2✔
415

416
      return DEFAULT_FALLBACK_LANGUAGE
2✔
417
    end
418

419
    # Changes the install.inf in inst-sys according to newly selected language
420
    #
421
    # FIXME: code just moved, refactoring needed
422
    def adapt_install_inf
1✔
423
      yinf = {}
3✔
424
      yinf_ref = arg_ref(yinf)
3✔
425
      AsciiFile.SetDelimiter(yinf_ref, " ")
3✔
426
      yinf = yinf_ref.value
3✔
427
      yinf_ref = arg_ref(yinf)
3✔
428
      AsciiFile.ReadFile(yinf_ref, "/etc/yast.inf")
3✔
429
      yinf = yinf_ref.value
3✔
430
      lines = AsciiFile.FindLineField(yinf, 0, "Language:")
3✔
431

432
      if Ops.greater_than(Builtins.size(lines), 0)
3✔
433
        yinf_ref = arg_ref(yinf)
2✔
434
        AsciiFile.ChangeLineField(
2✔
435
          yinf_ref,
436
          Ops.get_integer(lines, 0, -1),
437
          1,
438
          @language
439
        )
440
        yinf = yinf_ref.value
2✔
441
      else
442
        yinf_ref = arg_ref(yinf)
1✔
443
        AsciiFile.AppendLine(yinf_ref, ["Language:", @language])
1✔
444
        yinf = yinf_ref.value
1✔
445
      end
446

447
      yinf_ref = arg_ref(yinf)
3✔
448
      AsciiFile.RewriteFile(yinf_ref, "/etc/yast.inf")
3✔
449
      yinf = yinf_ref.value
3✔
450
    end
451

452
    # Set module to selected language.
453
    #
454
    # @param [String] lang language string ISO code of language
455
    def Set(lang)
1✔
456
      lang = deep_copy(lang)
23✔
457

458
      if @language != lang
23✔
459
        log.info("Language changed from #{@language} to #{lang}")
21✔
460

461
        lang = correct_language(lang)
21✔
462
        @language = lang
21✔
463

464
        if Stage.initial && !Mode.test && !Mode.live_installation
21✔
465
          integrate_inst_sys_extension(lang)
4✔
466
        end
467

468
        GetLocales() if Builtins.size(@locales) == 0
21✔
469
        language_def = GetLanguagesMap(false).fetch(lang, [])
21✔
470

471
        # In config mode, use language name translated into the current language
472
        # othewrwise use the language name translated into that selected language
473
        # because the whole UI will get translated later too
474
        @name = (Mode.config ? language_def[4] : language_def[0]) || lang
21✔
475
        Encoding.SetEncLang(@language)
21✔
476
      else
477
        log.info("Language unchanged: #{@language}")
2✔
478
      end
479

480
      if Stage.initial && !Mode.test
23✔
481
        adapt_install_inf
4✔
482

483
        # update "name" for proposal when it cannot be shown correctly
484
        if GetTextMode() && CJKLanguage(lang) && !CJKLanguage(@preselected)
4✔
485
          @name = GetLanguagesMap(false).fetch(lang, [])[1] || lang
1✔
486
        end
487
      end
488

489
      nil
23✔
490
    end
491

492

493
    # Set the language that was read from sysconfig,
494
    # read only one needed language file
495
    def QuickSet(lang)
1✔
496
      Builtins.y2milestone(
69✔
497
        "original language: %1; setting to lang:%2",
498
        @language,
499
        lang
500
      )
501

502
      if @language != lang
69✔
503
        lang_map = ReadLanguageMap(lang)
×
504
        @name = Ops.get_string(lang_map, [lang, 0], lang)
×
505
        @language = lang
×
506
        Encoding.SetEncLang(@language)
×
507
      end
508

509
      nil
69✔
510
    end
511

512
    def LinuxrcLangSet
1✔
513
      @linuxrc_language_set
×
514
    end
515

516
    # generate the whole locale string for given language according to DB
517
    # (e.g. de_DE -> de_DE.UTF-8)
518
    def GetLocaleString(lang)
1✔
519

520
      # if the suffix is already there, do nothing
521
      return lang if lang.count(".@") > 0
29✔
522

523
      read_languages_map if Builtins.size(@languages_map) == 0
28✔
524

525
      language_info = Ops.get(@languages_map, lang, [])
28✔
526
      if !Builtins.haskey(@languages_map, lang)
28✔
527
        language_info = [lang, lang, ".UTF-8"]
6✔
528
      end
529

530
      # full language code
531
      idx = @use_utf8 ? 2 : 3
28✔
532
      val = lang + (language_info[idx] || "")
28✔
533

534
      Builtins.y2milestone("locale %1", val)
28✔
535
      val
28✔
536
    end
537

538

539
    # Store current language as default language.
540
    def SetDefault
1✔
541
      Builtins.y2milestone("Setting default language: %1", @language)
80✔
542
      @default_language = @language
80✔
543
      nil
80✔
544
    end
545

546
    # read the localed.conf language
547
    def ReadLocaleConfLanguage
1✔
548
      return nil if Mode.testsuite
72✔
549
      @localed_conf  = Y2Country.read_locale_conf
72✔
550
      return nil if @localed_conf.nil?
72✔
551
      local_lang = @localed_conf["LANG"]
2✔
552
      local_lang = local_lang.sub(/[.@].*$/, "") if local_lang
2✔
553
      log.info("language from locale.conf: %{local_lang}")
2✔
554
      local_lang
2✔
555
    end
556

557
    # Read the rest of language values from sysconfig
558
    def ReadSysconfigValues
1✔
559
      @languages = Misc.SysconfigRead(
72✔
560
        path(".sysconfig.language.INSTALLED_LANGUAGES"),
561
        ""
562
      )
563

564
      nil
72✔
565
    end
566

567
    # read UTF-8 status from localed.conf
568
    def ReadUtf8Setting
1✔
569
      if Mode.testsuite
72✔
570
        @use_utf8 = true
×
571
        return nil
×
572
      end
573
      # during live installation, we have local configuration
574
      if !Stage.initial || Mode.live_installation
72✔
575
        @localed_conf  = Y2Country.read_locale_conf
72✔
576
        return nil if @localed_conf.nil?
72✔
577
        local_lang = @localed_conf["LANG"]
2✔
578
        @use_utf8 = local_lang.include?(".UTF-8") unless (local_lang.nil? || local_lang.empty?)
2✔
579
      else
580
        @use_utf8 = true
×
581
      end
582
      log.info("Use UTF-8: #{@use_utf8}")
2✔
583
    end
584

585
    # Constructor
586
    #
587
    # Initializes module either from /etc/install.inf
588
    # or from /etc/sysconfig/language
589
    def Language
1✔
590
      if Mode.config
69✔
591
        # read the translated name: bug #180633
592
        read_languages_map
×
593
        @name = Ops.get_string(@languages_map, [@language, 4], @language)
×
594
        return
×
595
      end
596

597
      if Stage.initial && !Mode.live_installation
69✔
598
        lang = ProductFeatures.GetStringFeature("globals", "language")
×
599
        Builtins.y2milestone("product LANGUAGE %1", lang)
×
600

601
        @preselected = Linuxrc.InstallInf("Locale")
×
602
        Builtins.y2milestone("install_inf Locale %1", @preselected)
×
603
        if @preselected != nil && @preselected != ""
×
604
          lang = @preselected
×
605
          @linuxrc_language_set = true if lang != DEFAULT_FALLBACK_LANGUAGE
×
606
        else
607
          @preselected = DEFAULT_FALLBACK_LANGUAGE
×
608
        end
609

610
        lang ||= ""
×
611
        Builtins.y2milestone("lang after checking /etc/install.inf: %1", lang)
×
612
        if lang == ""
×
613
          # As language has not been set we are trying to ask libzypp.
614
          # But libzypp can also returns languages which we do not support
615
          # (e.g. default "en"). So we are checking and changing it to default
616
          # if needed (without showing an error (bnc#1009508))
617
          lang = correct_language(Pkg.GetTextLocale, error_report: false)
×
618
          Builtins.y2milestone("setting lang to default language: %1", lang)
×
619
        end
620
        # Ignore any previous settings and take language from control file.
621
        l = ProductFeatures.GetStringFeature("globals", "language")
×
622
        if l != nil && l != ""
×
623
          lang = l
×
624
          Builtins.y2milestone(
×
625
            "setting lang to ProductFeatures::language: %1",
626
            lang
627
          )
628
        end
629
        FillEnglishNames()
×
630
        Set(lang) # coming from /etc/install.inf
×
631
        SetDefault() # also default
×
632
      else
633
        local_lang = ReadLocaleConfLanguage() || @language
69✔
634
        QuickSet(local_lang)
69✔
635
        SetDefault() # also default
69✔
636
        if Mode.live_installation || Stage.firstboot
69✔
637
          FillEnglishNames()
×
638
        end
639
      end
640
      if Ops.greater_than(
69✔
641
          SCR.Read(path(".target.size"), "/etc/sysconfig/language"),
642
          0
643
        )
644
        ReadSysconfigValues()
69✔
645
        ReadUtf8Setting()
69✔
646
      end
647
      Encoding.SetUtf8Lang(@use_utf8)
69✔
648

649
      nil
69✔
650
    end
651

652
    # Store the inital values; in normal mode, read from system was done in constructor
653
    # @param [Boolean] really: also read the values from the system
654
    def Read(really)
1✔
655
      if really
12✔
656
        Set(ReadLocaleConfLanguage() || @language)
3✔
657
        ReadSysconfigValues()
3✔
658
        ReadUtf8Setting()
3✔
659
      end
660

661
      @language_on_entry = @language
12✔
662
      @languages_on_entry = @languages
12✔
663

664
      Builtins.y2milestone(
12✔
665
        "language: %1, languages: %2",
666
        @language_on_entry,
667
        @languages_on_entry
668
      )
669

670
      @ExpertSettingsChanged = false
12✔
671

672
      true
12✔
673
    end
674

675
    # was anything modified?
676
    def Modified
1✔
677
      @language != @language_on_entry || @ExpertSettingsChanged ||
1✔
678
        Builtins.sort(Builtins.splitstring(@languages, ",")) !=
679
          Builtins.sort(Builtins.splitstring(@languages_on_entry, ","))
680
    end
681

682
    # Does the modification of language(s) require installation of new packages?
683
    # This test compares the list of original languages (primary+secondary) with
684
    # the list after user's modifications
685
    def PackagesModified
1✔
686
      Builtins.sort(
×
687
        Builtins.union(Builtins.splitstring(@languages, ","), [@language])
688
      ) !=
689
        Builtins.sort(
690
          Builtins.union(
691
            Builtins.splitstring(@languages_on_entry, ","),
692
            [@language_on_entry]
693
          )
694
        )
695
    end
696

697
    # Return the values for the various expert settings in a map
698
    #
699
    # @param       -
700
    #
701
    # @return  [Hash] with values filled in
702
    #
703
    def GetExpertValues
1✔
704
      { "use_utf8" => @use_utf8 }
3✔
705
    end
706

707
    # Set the values of the various expert setting
708
    #
709
    # @param [Hash] val     map with new values of expert settings
710
    #
711
    # @return  [void]
712
    #
713
    def SetExpertValues(val)
1✔
714
      val = deep_copy(val)
10✔
715
      if Builtins.haskey(val, "use_utf8")
10✔
716
        @use_utf8 = Ops.get_boolean(val, "use_utf8", false)
5✔
717
        Encoding.SetUtf8Lang(@use_utf8)
5✔
718
      end
719

720
      nil
10✔
721
    end
722

723
    # Set the given language in WFM and UI
724
    #
725
    # @param       language (could be different from current in CJK case)
726
    #
727
    # @return      -
728
    def WfmSetGivenLanguage(lang)
1✔
729
      return if Mode.config
7✔
730

731
      encoding = @use_utf8 ? "UTF-8" : Encoding.console
7✔
732
      log.info("Language changed from #{@language} to #{lang} encoding: #{encoding} use_utf8: #{@use_utf8}")
7✔
733

734
      UI.SetLanguage(lang, encoding)
7✔
735

736
      if @use_utf8
7✔
737
        WFM.SetLanguage(lang, "UTF-8")
7✔
738
      else
739
        WFM.SetLanguage(lang)
×
740
      end
741

742
      # Force rebuilding the languages map to make sure the correct translations are used
743
      read_languages_map
7✔
744

745
      nil
7✔
746
    end
747

748

749
    # Set the current language in WFM and UI
750
    #
751
    # @param       -
752
    #
753
    # @return      -
754
    def WfmSetLanguage
1✔
755
      log.info("Setting the current language")
1✔
756
      WfmSetGivenLanguage(@language)
1✔
757

758
      nil
1✔
759
    end
760

761

762
    # Return proposal string.
763
    #
764
    # @return        [Array<String>]        user readable description.
765
    #                If force_reset is true reset the module to the language
766
    #                stored in default_language.
767
    def MakeProposal(force_reset, language_changed)
1✔
768
      Builtins.y2milestone("force_reset: %1", force_reset)
4✔
769
      Builtins.y2milestone("language_changed: %1", language_changed)
4✔
770

771
      if force_reset
4✔
772
        Set(@default_language) # reset
1✔
773
      end
774
      ret = [
775
        # summary label
776
        Builtins.sformat(_("Primary Language: %1"), @name)
4✔
777
      ]
778
      if Builtins.size(@languages_map) == 0 || language_changed
4✔
779
        read_languages_map
3✔
780
      end
781
      # maybe additional languages were selected in package selector (bnc#393007)
782
      langs = Builtins.splitstring(@languages, ",")
4✔
783
      missing = []
4✔
784
      Builtins.foreach(Pkg.GetAdditionalLocales) do |additional|
4✔
785
        # add the language for both kind of values ("cs" vs. "pt_PT")
786
        if !Builtins.contains(langs, additional)
×
787
          additional = DEFAULT_FALLBACK_LANGUAGE if additional == "en"
×
788
          additional = "pt_PT" if additional == "pt"
×
789
          if Builtins.haskey(@languages_map, additional)
×
790
            missing = Builtins.add(missing, additional)
×
791
            next
×
792
          end
793
          if Builtins.contains(langs, additional) #en_US or pt_PT already installed
×
794
            next
×
795
          end
796
          # now, let's hope there's only one full entry for the short one
797
          # (e.g. cs_CZ for cs)
798
          Builtins.foreach(@languages_map) do |k, dummy|
×
799
            if Builtins.substring(k, 0, 2) == additional
×
800
              missing = Builtins.add(missing, k)
×
801
              raise Break
×
802
            end
803
          end
804
        end
805
      end
806
      if Ops.greater_than(Builtins.size(missing), 0)
4✔
807
        langs = Convert.convert(
×
808
          Builtins.union(langs, missing),
809
          :from => "list",
810
          :to   => "list <string>"
811
        )
812
        @languages = Builtins.mergestring(langs, ",")
×
813
      end
814
      # now, generate the summary strings
815
      if @languages != "" && @languages != @language
4✔
816
        langs = []
×
817
        Builtins.foreach(Builtins.splitstring(@languages, ",")) do |lang|
×
818
          if lang != @language
×
819
            l = Ops.get_string(
×
820
              @languages_map,
821
              [lang, 4],
822
              Ops.get_string(@languages_map, [lang, 0], "")
823
            )
824
            langs = Builtins.add(langs, l) if l != ""
×
825
          end
826
        end
827
        if Ops.greater_than(Builtins.size(langs), 0)
×
828
          # summary label
829
          ret = Builtins.add(
×
830
            ret,
831
            Builtins.sformat(
832
              _("Additional Languages: %1"),
833
              Builtins.mergestring(langs, ", ")
834
            )
835
          )
836
        end
837
      end
838
      deep_copy(ret)
4✔
839
    end
840

841
    # Return 'simple' proposal string.
842
    # @return [String] preformated description.
843
    def MakeSimpleProposal
1✔
844
      Yast.import "HTML"
2✔
845

846
      ret = [
847
        # summary label
848
        Builtins.sformat(_("Primary Language: %1"), @name)
2✔
849
      ]
850
      if @languages != "" && @languages != @language
2✔
851
        langs = []
×
852
        Builtins.foreach(Builtins.splitstring(@languages, ",")) do |lang|
×
853
          if lang != @language
×
854
            l = Ops.get_string(
×
855
              @languages_map,
856
              [lang, 4],
857
              Ops.get_string(@languages_map, [lang, 0], "")
858
            )
859
            langs = Builtins.add(langs, l) if l != ""
×
860
          end
861
        end
862
        if Ops.greater_than(Builtins.size(langs), 0)
×
863
          # summary label
864
          ret = Builtins.add(
×
865
            ret,
866
            Builtins.sformat(_("Additional Languages: %1"), HTML.List(langs))
867
          )
868
        end
869
      end
870
      HTML.List(ret)
2✔
871
    end
872

873
    # return user readable description of language
874
    def GetName
1✔
875
      @name
3✔
876
    end
877

878
    # Return a map of ids and names to build up a selection list
879
    # for the user. The key is used later in the Set function
880
    # to select this language. The name is a translated string.
881
    #
882
    # @return [Hash] of $[ language : [ utf8-name, ascii-name] ...]
883
    #                        for all known languages
884
    #                        'language' is the (2 or 5 char)  ISO language code.
885
    #                        'utf8-name' is a user-readable (UTF-8 encoded !) string.
886
    #                        'ascii-name' is an english (ascii encoded !) string.
887
    # @see #Set
888
    def Selection
1✔
889
      read_languages_map
×
890

891
      Builtins.mapmap(@languages_map) do |code, data|
×
892
        {
893
          code => [
×
894
            Ops.get_string(data, 0, ""),
895
            Ops.get_string(data, 1, ""),
896
            Ops.get_string(data, 4, Ops.get_string(data, 0, ""))
897
          ]
898
        }
899
      end
900
    end
901

902
    # Save state to target.
903
    def Save
1✔
904
      SCR.Write(path(".sysconfig.language.INSTALLED_LANGUAGES"), @languages)
10✔
905
      SCR.Write(path(".sysconfig.language"), nil)
10✔
906

907
      run_locale_command(locale)
10✔
908
      nil
9✔
909
    rescue Cheetah::ExecutionFailed => e
910
      log.error "Language configuration not written: #{e.inspect}"
1✔
911
      log.error "stderr: #{e.stderr}"
1✔
912

913
      # TRANSLATORS: the "%s" is replaced by the executed command
914
      Report.Error(_("Could not save the language setting, the command\n%s\nfailed.") % e.commands.first.join(' '))
1✔
915
      nil
1✔
916
    end
917

918
    # unselect all selected packages (bnc#439373)
919
    #
920
    # this is a workaround for installing recommened packages for already
921
    # installed packages - we cannot simply set the solver flag
922
    # as the laguage packages are also recommended, this would cause that
923
    # no language package would be installed
924
    #
925
    # do this just once at the beginning
926
    def ResetRecommendedPackages
1✔
927
      return if !@reset_recommended
1✔
928

929
      Pkg.PkgSolve(true)
1✔
930

931
      # package names only, without version
932
      names_only = true
1✔
933
      selected_packages = Pkg.GetPackages(:selected, names_only)
1✔
934

935
      log.info("Unselecting already recommended packages: #{selected_packages.inspect}")
1✔
936
      selected_packages.each { |p| Yast::Pkg.PkgNeutral(p) }
2✔
937

938
      @reset_recommended = false
1✔
939

940
      nil
1✔
941
    end
942

943
    # Initializes source and target,
944
    # computes the packages necessary to install and uninstall,
945
    # @return false if the solver failed (unresolved dependencies)
946
    def PackagesInit(selected_languages)
1✔
947
      selected_languages = deep_copy(selected_languages)
×
948
      PackageSystem.EnsureSourceInit
×
949
      PackageSystem.EnsureTargetInit
×
950

951
      solver_flags_backup = Pkg.GetSolverFlags
×
952
      # first, do not ignore recommended (= also language) packages
953
      Pkg.SetSolverFlags({ "ignoreAlreadyRecommended" => false })
×
954
      # ... but skip non-language recommended packages
955
      ResetRecommendedPackages()
×
956

957
      Pkg.SetAdditionalLocales(selected_languages)
×
958

959
      # now, add only recommended language packages (other recommended are PkgNeutral-ized)
960
      solved = Pkg.PkgSolve(true)
×
961

962
      Pkg.SetSolverFlags(solver_flags_backup)
×
963

964
      solved
×
965
    end
966

967
    # checks for disk space (#50745)
968
    # @return false when there is not enough disk space for new packages
969
    def EnoughSpace
1✔
970
      ok = true
×
971
      Builtins.foreach(Pkg.TargetGetDU) do |mountpoint, usage|
×
972
        if Ops.greater_than(Ops.get(usage, 2, 0), Ops.get(usage, 0, 0))
×
973
          ok = false
×
974
        end
975
      end
976
      ok
×
977
    end
978

979
    # Install and uninstall packages selected by Pkg::SetAdditionalLocales
980
    def PackagesCommit
1✔
981
      check_source
×
982

983
      if !Mode.commandline
×
984
        # work-around for following in order not to depend on yast2-packager
985
        #        PackageSlideShow::InitPkgData (false);
986
        #               "value" : PackageSlideShow::total_size_to_install / 1024 , // kilobytes
987
        total_sizes_per_cd_per_src = Pkg.PkgMediaSizes
×
988
        total_size_to_install = 0
×
989
        Builtins.foreach(Builtins.flatten(total_sizes_per_cd_per_src)) do |item|
×
990
          if item != -1
×
991
            total_size_to_install = Ops.add(total_size_to_install, item)
×
992
          end
993
        end
994

995
        SlideShow.Setup(
×
996
          [
997
            {
998
              "name"        => "packages",
999
              "description" => _("Installing Packages..."),
1000
              "value"       => Ops.divide(total_size_to_install, 1024), # kilobytes
1001
              "units"       => :kb
1002
            }
1003
          ]
1004
        )
1005

1006
        SlideShow.ShowTable
×
1007

1008
        SlideShow.OpenDialog
×
1009
        SlideShow.MoveToStage("packages")
×
1010
      end
1011
      Pkg.PkgCommit(0)
×
1012
      SlideShow.CloseDialog if !Mode.commandline
×
1013
      true
×
1014
    end
1015

1016
    # de_DE@UTF-8 -> "DE"
1017
    # @return country part of language
1018
    def GetGivenLanguageCountry(lang)
1✔
1019
      country = lang
4✔
1020

1021
      country = @default_language if country == nil || country == ""
4✔
1022
      if country != nil && country != ""
4✔
1023
        if Builtins.find(country, "@") != -1
4✔
1024
          country = Ops.get(Builtins.splitstring(country, "@"), 0, "")
1✔
1025
        end
1026
      end
1027
      if country != nil && country != ""
4✔
1028
        if Builtins.find(country, ".") != -1
4✔
1029
          country = Ops.get(Builtins.splitstring(country, "."), 0, "")
1✔
1030
        end
1031
      end
1032
      if country != nil && country != ""
4✔
1033
        if Builtins.find(country, "_") != -1
4✔
1034
          country = Ops.get(Builtins.splitstring(country, "_"), 1, "")
3✔
1035
        else
1036
          country = Builtins.toupper(country)
1✔
1037
        end
1038
      end
1039

1040
      Builtins.y2debug("country=%1", country)
4✔
1041
      country
4✔
1042
    end
1043

1044

1045
    # de_DE@UTF-8 -> "DE"
1046
    # @return country part of language
1047
    def GetLanguageCountry
1✔
1048
      GetGivenLanguageCountry(@language)
×
1049
    end
1050

1051

1052
    # Returns true if translation for given language is not complete
1053
    def IncompleteTranslation(lang)
1✔
1054
      if !Builtins.haskey(@translation_status, lang)
2✔
1055
        file = "/usr/lib/YaST2/trans/#{lang}.status"
2✔
1056
        if !FileUtils.Exists(file)
2✔
1057
          ll = main_language(lang)
×
1058
          file = "/usr/lib/YaST2/trans/#{ll}.status" unless ll.empty?
×
1059
        end
1060

1061
        status = SCR.Read(path(".target.string"), file)
2✔
1062

1063
        if status != nil && status != ""
2✔
1064
          to_i = Builtins.tointeger(status)
2✔
1065
          Ops.set(@translation_status, lang, to_i != nil ? to_i : 0)
2✔
1066
        else
1067
          Ops.set(@translation_status, lang, 100)
×
1068
        end
1069
      end
1070
      treshold = Builtins.tointeger(
2✔
1071
        ProductFeatures.GetStringFeature(
1072
          "globals",
1073
          "incomplete_translation_treshold"
1074
        )
1075
      )
1076
      treshold = 95 if treshold == nil
2✔
1077

1078
      Ops.less_than(Ops.get(@translation_status, lang, 0), treshold)
2✔
1079
    end
1080

1081
    # Checks if translation is complete and displays
1082
    # Continue/Cancel popup messsage if it is not
1083
    # return true if translation is OK or user agrees with the warning
1084
    def CheckIncompleteTranslation(lang)
1✔
1085
      if IncompleteTranslation(@language)
×
1086
        # continue/cancel message
1087
        return Popup.ContinueCancel(
×
1088
          _(
1089
            "Translation of the primary language is not complete.\nSome texts may be displayed in English.\n"
1090
          )
1091
        )
1092
      end
1093
      true
×
1094
    end
1095

1096
    # Initial language
1097
    # @return default language
1098
    def default_language
1✔
1099
      DEFAULT_FALLBACK_LANGUAGE
5✔
1100
    end
1101

1102
    # AutoYaST interface function: Get the Language configuration from a map.
1103
    # @param [Hash] settings imported map
1104
    # @return success
1105
    def Import(settings)
1✔
1106
      settings = deep_copy(settings)
6✔
1107
      Read(false) if @languages_on_entry == "" # only save original values
6✔
1108

1109
      Set(Ops.get_string(settings, "language", @language))
6✔
1110
      @languages = Ops.get_string(settings, "languages", @languages)
6✔
1111

1112
      SetExpertValues(settings)
6✔
1113

1114
      llanguages = Builtins.splitstring(@languages, ",")
6✔
1115
      if !Builtins.contains(llanguages, @language)
6✔
1116
        llanguages = Builtins.add(llanguages, RemoveSuffix(@language))
4✔
1117
        @languages = Builtins.mergestring(llanguages, ",")
4✔
1118
      end
1119
      # set the language dependent packages to install
1120
      if Mode.autoinst
6✔
1121
        Pkg.SetPackageLocale(@language)
2✔
1122
        Pkg.SetAdditionalLocales(Builtins.splitstring(@languages, ","))
2✔
1123
      end
1124

1125
      true
6✔
1126
    end
1127

1128
    # AutoYaST interface function: Return the Language configuration as a map.
1129
    # @return [Hash] with the settings
1130
    def Export
1✔
1131
      ret = {}
4✔
1132
      if @language == default_language
4✔
1133
        log.info("language <#{@language}> is the default language "\
3✔
1134
          "--> no export")
1135
      else
1136
        ret["language"] = @language
1✔
1137
      end
1138

1139
      if @languages.empty?
4✔
1140
        log.info("empty languages --> no export")
3✔
1141
      else
1142
        ret["languages"] = @languages
1✔
1143
      end
1144

1145
      ret["use_utf8"] = @use_utf8 if !@use_utf8
4✔
1146
      deep_copy(ret)
4✔
1147
    end
1148

1149
    # AutoYaST interface function: Return the summary of Language configuration as a map.
1150
    # @return summary string
1151
    def Summary
1✔
1152
      MakeSimpleProposal()
2✔
1153
    end
1154

1155
    # kind: `first_screen, `primary, `secondary
1156
    def GetLanguageItems(kind)
1✔
1157
      ret = []
×
1158

1159
      # already generated in previous run with `primary
1160
      if kind == :secondary && @secondary_items != []
×
1161
        return deep_copy(@secondary_items)
×
1162
      end
1163
      @secondary_items = []
×
1164

1165
      use_ascii = GetTextMode()
×
1166

1167
      en_name_sort = Builtins.mapmap(Selection()) do |code, info|
×
1168
        english = EnglishName(code, Ops.get_string(info, 2, code))
×
1169
        { english => [Ops.get_string(info, use_ascii ? 1 : 0, ""), code] }
×
1170
      end
1171
      if kind == :first_screen
×
1172
        # fate 301789
1173
        # English name of language (translated language).
1174
        # e.g. German (Deutsch)
1175
        ret = Builtins.maplist(en_name_sort) do |name, codelist|
×
1176
          label = Builtins.substring(Ops.get_string(codelist, 1, ""), 0, 2) == "en" ?
×
1177
            Ops.get_string(codelist, 0, "") :
×
1178
            Builtins.sformat("%1 - %2", name, Ops.get_string(codelist, 0, ""))
×
1179
          Item(Id(Ops.get_string(codelist, 1, "")), label)
×
1180
        end
1181
        return deep_copy(ret)
×
1182
      end
1183
      # sort language by ASCII with help of a map
1184
      # $[ "ascii-name" : [ "user-readable-string", "code" ], ...]
1185
      # the "user-readable-string" is either ascii or utf8, depending
1186
      # on textmode probed above (workaround because there isn't any
1187
      # usable console font for all languages).
1188

1189
      languageselsort = Builtins.mapmap(Selection()) do |lang_code, lang_info|
×
1190
        key = Ops.get_string(lang_info, 1, lang_code)
×
1191
        {
1192
          key => [
×
1193
            Ops.get_string(lang_info, use_ascii ? 1 : 0, ""),
×
1194
            lang_code,
1195
            Ops.get_string(lang_info, 2, key)
1196
          ]
1197
        }
1198
      end
1199

1200
      # mapping of language name (translated) to language code
1201
      lang2code = {}
×
1202
      # mapping language code to native form
1203
      code2native = {}
×
1204
      # list of language names (translated)
1205
      lang_list = []
×
1206
      Builtins.foreach(languageselsort) do |name, codelist|
×
1207
        Ops.set(
×
1208
          lang2code,
1209
          Ops.get_string(codelist, 2, ""),
1210
          Ops.get_string(codelist, 1, "")
1211
        )
1212
        lang_list = Builtins.add(lang_list, Ops.get_string(codelist, 2, ""))
×
1213
        Ops.set(
×
1214
          code2native,
1215
          Ops.get_string(codelist, 1, ""),
1216
          Ops.get_string(codelist, 0, "")
1217
        )
1218
      end
1219

1220

1221
      if Stage.firstboot
×
1222
        # show also native forms in firstboot (bnc#492812)
1223
        ret = Builtins.maplist(en_name_sort) do |name, codelist|
×
1224
          code = Ops.get_string(codelist, 1, "")
×
1225
          label = Builtins.substring(code, 0, 2) == "en" ?
×
1226
            Ops.get_string(codelist, 0, "") :
×
1227
            Builtins.sformat("%1 - %2", name, Ops.get_string(codelist, 0, ""))
×
1228
          Item(Id(code), label, @language == code)
×
1229
        end
1230
        return deep_copy(ret)
×
1231
      end
1232
      primary_included = false
×
1233

1234
      if kind == :primary || kind == :secondary
×
1235
        languages_l = Builtins.splitstring(@languages, ",")
×
1236
        # filter the primary language from the list of secondary ones:
1237
        languages_l = Builtins.filter(languages_l) { |l| l != @language }
×
1238

1239
        icons = !(Stage.initial || Stage.firstboot)
×
1240
        primary_items = []
×
1241
        @secondary_items = Builtins.maplist(Builtins.lsort(lang_list)) do |trans_lang|
×
1242
          code = Ops.get_string(lang2code, trans_lang, "")
×
1243
          show_lang = @language == code ?
×
1244
            trans_lang :
×
1245
            Builtins.sformat(
×
1246
              "%1 - %2",
1247
              trans_lang,
1248
              Ops.get_string(code2native, code, "")
1249
            )
1250
          primary_items = Builtins.add(
×
1251
            primary_items,
1252
            icons ?
×
1253
              Item(
×
1254
                Id(code),
1255
                term(
1256
                  :icon,
1257
                  Ops.add(
1258
                    Builtins.tolower(GetGivenLanguageCountry(code)),
1259
                    "/flag.png"
1260
                  )
1261
                ),
1262
                show_lang,
1263
                @language == code
1264
              ) :
1265
              Item(Id(code), trans_lang, @language == code)
×
1266
          )
1267
          primary_included = true if @language == code
×
1268
          icons ?
×
1269
            Item(
×
1270
              Id(code),
1271
              term(
1272
                :icon,
1273
                Ops.add(
1274
                  Builtins.tolower(GetGivenLanguageCountry(code)),
1275
                  "/flag.png"
1276
                )
1277
              ),
1278
              trans_lang,
1279
              Builtins.contains(languages_l, code)
1280
            ) :
1281
            Item(Id(code), trans_lang, Builtins.contains(languages_l, code))
×
1282
        end
1283
        if !primary_included
×
1284
          primary_items = Builtins.add(
×
1285
            primary_items,
1286
            Item(Id(@language), @language, true)
1287
          )
1288
        end
1289
        ret = kind == :primary ? primary_items : @secondary_items
×
1290
      end
1291
      deep_copy(ret)
×
1292
    end
1293

1294
    # check if selected language has support on media (F301238)
1295
    # show a warning when not
1296
    # @deprecated does nothing
1297
    def CheckLanguagesSupport(_selected_language)
1✔
1298
      log.warn "Called check for language support, but it does nothing"
×
1299
      return
×
1300
    end
1301

1302
    # Set current YaST language to English if method for showing text in
1303
    # current language is not supported:
1304
    #
1305
    # * CJK languages when not using fbiterm
1306
    # * other unsupported languages when not running on fbiterm
1307
    #
1308
    # See http://bugzilla.suse.com/show_bug.cgi?id=479529 for discussion
1309
    # @boolean show_popup if information popup about the change should be shown
1310
    # @return true if UI language was changed
1311
    def SwitchToEnglishIfNeeded(show_popup)
1✔
1312
      return false if Stage.normal || supported_language?(@language)
16✔
1313

1314
      show_popup_early = supported_language?(WFM.GetLanguage())
8✔
1315
      show_fallback_to_english_warning if show_popup && show_popup_early
8✔
1316
      WfmSetGivenLanguage(DEFAULT_FALLBACK_LANGUAGE)
8✔
1317
      show_fallback_to_english_warning if show_popup && !show_popup_early
8✔
1318
      true
8✔
1319
    end
1320

1321
    # Determines whether the language is supported or not
1322
    #
1323
    # @note To be used in instsys
1324
    def supported_language?(lang)
1✔
1325
      return true unless GetTextMode()
21✔
1326
      (fbiterm? && supported_by_fbiterm?(lang)) || !(fbiterm? || CJKLanguage(lang))
21✔
1327
    end
1328

1329
    def GetCurrentLocaleString
1✔
1330
      GetLocaleString @language
7✔
1331
    end
1332

1333
    # Returns main language from full locale. E.g. en_US.UTF-8 -> en or agr_PE -> agr
1334
    def main_language(lang)
1✔
1335
      return "" unless lang
50✔
1336
      lang[/^[a-z]+/]
49✔
1337
    end
1338

1339
    publish :variable => :language, :type => "string"
1✔
1340
    publish :variable => :language_on_entry, :type => "string"
1✔
1341
    publish :variable => :preselected, :type => "string"
1✔
1342
    publish :variable => :languages, :type => "string"
1✔
1343
    publish :variable => :languages_on_entry, :type => "string"
1✔
1344
    publish :variable => :ExpertSettingsChanged, :type => "boolean"
1✔
1345
    publish :variable => :selection_skipped, :type => "boolean"
1✔
1346
    publish :variable => :available_lang_filenames, :type => "list <string>"
1✔
1347
    publish :function => :RemoveSuffix, :type => "string (string)"
1✔
1348
    publish :function => :default_language, :type => "string ()"
1✔
1349
    publish :function => :CJKLanguage, :type => "boolean (string)"
1✔
1350
    publish :function => :GetTextMode, :type => "boolean ()"
1✔
1351
    publish :function => :GetLanguagesMap, :type => "map <string, list> (boolean)"
1✔
1352
    publish :function => :GetLang2TimezoneMap, :type => "map <string, string> (boolean)"
1✔
1353
    publish :function => :GetLang2KeyboardMap, :type => "map <string, string> (boolean)"
1✔
1354
    publish :function => :GetLocales, :type => "map <string, integer> ()"
1✔
1355
    publish :function => :Set, :type => "void (string)"
1✔
1356
    publish :function => :QuickSet, :type => "void (string)"
1✔
1357
    publish :function => :LinuxrcLangSet, :type => "boolean ()"
1✔
1358
    publish :function => :GetLocaleString, :type => "string (string)"
1✔
1359
    publish :function => :GetCurrentLocaleString, :type => "string ()"
1✔
1360
    publish :function => :SetDefault, :type => "void ()"
1✔
1361
    publish :function => :ReadSysconfigValues, :type => "void ()"
1✔
1362
    publish :function => :Language, :type => "void ()"
1✔
1363
    publish :function => :Read, :type => "boolean (boolean)"
1✔
1364
    publish :function => :Modified, :type => "boolean ()"
1✔
1365
    publish :function => :PackagesModified, :type => "boolean ()"
1✔
1366
    publish :function => :GetExpertValues, :type => "map ()"
1✔
1367
    publish :function => :SetExpertValues, :type => "void (map)"
1✔
1368
    publish :function => :WfmSetGivenLanguage, :type => "void (string)"
1✔
1369
    publish :function => :WfmSetLanguage, :type => "void ()"
1✔
1370
    publish :function => :MakeProposal, :type => "list <string> (boolean, boolean)"
1✔
1371
    publish :function => :MakeSimpleProposal, :type => "string ()"
1✔
1372
    publish :function => :GetName, :type => "string ()"
1✔
1373
    publish :function => :Selection, :type => "map <string, list> ()"
1✔
1374
    publish :function => :Save, :type => "void ()"
1✔
1375
    publish :function => :PackagesInit, :type => "boolean (list <string>)"
1✔
1376
    publish :function => :EnoughSpace, :type => "boolean ()"
1✔
1377
    publish :function => :PackagesCommit, :type => "boolean ()"
1✔
1378
    publish :function => :GetGivenLanguageCountry, :type => "string (string)"
1✔
1379
    publish :function => :GetLanguageCountry, :type => "string ()"
1✔
1380
    publish :function => :IncompleteTranslation, :type => "boolean (string)"
1✔
1381
    publish :function => :CheckIncompleteTranslation, :type => "boolean (string)"
1✔
1382
    publish :function => :Import, :type => "boolean (map)"
1✔
1383
    publish :function => :Export, :type => "map ()"
1✔
1384
    publish :function => :Summary, :type => "string ()"
1✔
1385
    publish :function => :GetLanguageItems, :type => "list <term> (symbol)"
1✔
1386
    publish :function => :CheckLanguagesSupport, :type => "void (string)"
1✔
1387
    publish :function => :SwitchToEnglishIfNeeded, :type => "boolean (boolean)"
1✔
1388

1389
  private
1✔
1390

1391
    # @return [Array<String>] List of languages which are not supported by fbiterm
1392
    UNSUPPORTED_FBITERM_LANGS = ["ar", "bn", "gu", "hi", "km", "mr", "pa", "ta", "th"].freeze
1✔
1393

1394
    # Checking available source repos
1395
    def check_source
1✔
1396
      if Pkg.SourceGetCurrent(true).empty?
×
1397
        Report.Warning(
×
1398
          _("There is no installation source enabled.\nThe corresponding translations will not be installed.")
1399
        )
1400
      end
1401
    end
1402

1403
    # Determines whether the language is supported by fbiterm
1404
    # @param lang [String] Language code
1405
    # @return [Boolean]
1406
    def supported_by_fbiterm?(lang)
1✔
1407
      code = main_language(lang)
10✔
1408
      UNSUPPORTED_FBITERM_LANGS.none?(code)
10✔
1409
    end
1410

1411
    # Determines whether the installer is running on fbiterm
1412
    #
1413
    # @return [Boolean]
1414
    def fbiterm?
1✔
1415
      Builtins.getenv("TERM") == "iterm"
36✔
1416
    end
1417

1418
    # Runs the command to make language settings persistent
1419
    #
1420
    # It is different depending on the stage, since for a not installed system it is needed to use
1421
    # the `systemd-firsrboot` tool, which does not work in a chroot
1422
    #
1423
    # @param locale [String]
1424
    # @raises [Cheetah::ExecutionFailed] when command failed to run
1425
    #
1426
    # @return [String] command that is invoked
1427
    def run_locale_command(locale)
1✔
1428
      if Stage.initial
10✔
1429
        # use --root option locally, running in chroot does not work in insts-sys
1430
        command = ["/usr/bin/systemd-firstboot", "--root", Installation.destdir, "--locale", locale]
2✔
1431

1432
        log.info("Making language settings persistent: #{command.join(' ')}")
2✔
1433

1434
        Yast::Execute.locally!(command)
2✔
1435
      else
1436
        prepare_locale_settings(locale)
8✔
1437

1438
        command = ["/usr/bin/localectl", "set-locale", *localectl_args]
8✔
1439

1440
        log.info("Making language settings persistent: #{command.join(' ')}")
8✔
1441

1442
        Yast::Execute.on_target!(command)
8✔
1443
      end
1444
    end
1445

1446
    # Determines whether language is read-only for the current product
1447
    #
1448
    # @return [Boolean] true if it's read-only; false otherwise.
1449
    def readonly
1✔
1450
      @readonly unless @readonly.nil?
10✔
1451
      @readonly = ProductFeatures.GetBooleanFeature("globals", "readonly_language")
10✔
1452
    end
1453

1454
    # Returns the locale to use, default or chosen
1455
    #
1456
    # Based on the readonly_language feature
1457
    #
1458
    # @return [String] default language when language set to read-only; chosen language otherwise
1459
    def locale
1✔
1460
      language = readonly ? DEFAULT_FALLBACK_LANGUAGE : @language
10✔
1461

1462
      GetLocaleString(language)
10✔
1463
    end
1464

1465
    # Sets locale settings for given locale
1466
    #
1467
    # @param locale [String]
1468
    def prepare_locale_settings(locale)
1✔
1469
      @localed_conf ||= {}
8✔
1470
      @localed_conf["LANG"] = locale
8✔
1471

1472
      fix_lc_messages(locale)
8✔
1473

1474
      log.info("Locale: #{@localed_conf}")
8✔
1475
    end
1476

1477
    # Fix the value of LC_MESSAGES for zh_HK locale
1478
    #
1479
    # It must be zh_TW
1480
    #
1481
    # @param locale [String]
1482
    def fix_lc_messages(locale)
1✔
1483
      @localed_conf ||= {}
8✔
1484

1485
      if locale.start_with?("zh_HK")
8✔
1486
        @localed_conf["LC_MESSAGES"] = "zh_TW"
2✔
1487
      elsif @localed_conf["LC_MESSAGES"] == "zh_TW"
6✔
1488
        @localed_conf.delete("LC_MESSAGES")
×
1489
      end
1490
    end
1491

1492
    # Returns the locale settings as args for localectl
1493
    #
1494
    # @return [Array<String>] a list of locale settings; e.g. ["LANG=zh_HK.UTF-8", "LC_MESAGES=zh_TW"]
1495
    def localectl_args
1✔
1496
      @localed_conf.map { |k, v| "#{k}=#{v}" }
18✔
1497
    end
1498

1499

1500
    def show_fallback_to_english_warning
1✔
1501
      Report.Message(
6✔
1502
        _(
1503
          "The selected language cannot be used in text mode. English is used for\ninstallation, but the selected language will be used for the new system."
1504
        )
1505
      )
1506
    end
1507
  end
1508

1509
  Language = LanguageClass.new
1✔
1510
  Language.main
1✔
1511
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc