From b33f8601e74d274318785ea1501e1bfbc5fffe01 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Wed, 23 Mar 2011 20:10:17 +0000 Subject: [PATCH] Improved selection based on language choices Convert all '_' to '-' to avoid problems with the format of $LANG vs xml:lang. Rank languages, instead of just testing whether we know them or not. By default, rank English lower than the current locale but better than other languages, since English is usually a good bet if your native language isn't available. --- tests/testreader.py | 4 ++-- zeroinstall/injector/model.py | 6 ++++-- zeroinstall/injector/solver.py | 45 ++++++++++++++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/tests/testreader.py b/tests/testreader.py index a92678b..f78473d 100755 --- a/tests/testreader.py +++ b/tests/testreader.py @@ -274,10 +274,10 @@ class TestReader(BaseTest): assert len(feed.implementations) == 3 assert len(feed.feeds) == 1, feed.feeds - self.assertEquals('fr en_GB', feed.feeds[0].langs) + self.assertEquals('fr en-GB', feed.feeds[0].langs) self.assertEquals('fr', feed.implementations['sha1=124'].langs) - self.assertEquals('fr en_GB', feed.implementations['sha1=234'].langs) + self.assertEquals('fr en-GB', feed.implementations['sha1=234'].langs) self.assertEquals('', feed.implementations['sha1=345'].langs) if __name__ == '__main__': diff --git a/zeroinstall/injector/model.py b/zeroinstall/injector/model.py index 5b8b20d..884228e 100644 --- a/zeroinstall/injector/model.py +++ b/zeroinstall/injector/model.py @@ -831,7 +831,9 @@ class ZeroInstallFeed(object): if not feed_src: raise InvalidInterface(_('Missing "src" attribute in ')) if feed_src.startswith('http:') or feed_src.startswith('https:') or local_path: - self.feeds.append(Feed(feed_src, x.getAttribute('arch'), False, langs = x.getAttribute('langs'))) + langs = x.getAttribute('langs') + if langs: langs = langs.replace('_', '-') + self.feeds.append(Feed(feed_src, x.getAttribute('arch'), False, langs = langs)) else: raise InvalidInterface(_("Invalid feed URL '%s'") % feed_src) else: @@ -931,7 +933,7 @@ class ZeroInstallFeed(object): impl.commands = commands impl.released = item_attrs.get('released', None) - impl.langs = item_attrs.get('langs', '') + impl.langs = item_attrs.get('langs', '').replace('_', '-') size = item.getAttribute('size') if size: diff --git a/zeroinstall/injector/solver.py b/zeroinstall/injector/solver.py index 5ac428c..3d8553c 100644 --- a/zeroinstall/injector/solver.py +++ b/zeroinstall/injector/solver.py @@ -95,15 +95,16 @@ class Solver(object): raise NotImplementedError("Abstract") class SATSolver(Solver): - __slots__ = ['_failure_reason', 'config', 'extra_restrictions', 'langs'] + """Converts the problem to a set of pseudo-boolean constraints and uses a PB solver to solve them. + @ivar langs: the preferred languages (e.g. ["es_ES", "en"]). Initialised to the current locale. + @type langs: str""" + + __slots__ = ['_failure_reason', 'config', 'extra_restrictions', '_lang_ranks', '_langs'] @property def iface_cache(self): return self.config.iface_cache # (deprecated; used by 0compile) - """Converts the problem to a set of pseudo-boolean constraints and uses a PB solver to solve them. - @ivar langs: the preferred languages (e.g. ["es_ES", "en"]). Initialised to the current locale. - @type langs: str""" def __init__(self, config, extra_restrictions = None): """ @param config: policy preferences (e.g. stability), the iface_cache and the stores to use @@ -116,7 +117,29 @@ class SATSolver(Solver): self.config = config self.extra_restrictions = extra_restrictions or {} - self.langs = [locale.getlocale()[0] or 'en'] + # By default, prefer the current locale's language first and English second + self.langs = [locale.getlocale()[0] or 'en', 'en'] + + def set_langs(self, langs): + """Set the preferred languages. + @param lang: languages (and regions), first choice first + @type lang: [str] + """ + # _lang_ranks is a map from locale string to score (higher is better) + _lang_ranks = {} + score = 0 + i = len(langs) + # (is there are duplicates, the first occurance takes precedence) + while i > 0: + i -= 1 + lang = langs[i].replace('_', '-') + _lang_ranks[lang.split('-')[0]] = score + _lang_ranks[lang] = score + 1 + score += 2 + self._langs = langs + self._lang_ranks = _lang_ranks + + langs = property(lambda self: self._langs, set_langs) def compare(self, interface, b, a, arch): """Compare a and b to see which would be chosen first. @@ -128,9 +151,10 @@ class SATSolver(Solver): # Languages we understand come first a_langs = (a.langs or 'en').split() b_langs = (b.langs or 'en').split() - main_langs = set(l.split('_')[0] for l in self.langs) - r = cmp(any(l.split('_')[0] in main_langs for l in a_langs), - any(l.split('_')[0] in main_langs for l in b_langs)) + my_langs = self._lang_ranks + + r = cmp(max(my_langs.get(l.split('-')[0], -1) for l in a_langs), + max(my_langs.get(l.split('-')[0], -1) for l in b_langs)) if r: return r a_stab = a.get_stability() @@ -184,8 +208,9 @@ class SATSolver(Solver): if r: return r # Slightly prefer languages specialised to our country - r = cmp(any(l in self.langs for l in a_langs), - any(l in self.langs for l in b_langs)) + # (we know a and b have the same base language at this point) + r = cmp(max(my_langs.get(l, -1) for l in a_langs), + max(my_langs.get(l, -1) for l in b_langs)) if r: return r # Slightly prefer cached versions -- 2.11.4.GIT