wbnews 1.58.3__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. wbnews/.coveragerc +23 -0
  2. wbnews/__init__.py +1 -0
  3. wbnews/admin.py +30 -0
  4. wbnews/apps.py +9 -0
  5. wbnews/factories.py +36 -0
  6. wbnews/filters/__init__.py +1 -0
  7. wbnews/filters/news.py +46 -0
  8. wbnews/fixtures/wbnews.yaml +1 -0
  9. wbnews/import_export/__init__.py +0 -0
  10. wbnews/import_export/backends/__init__.py +1 -0
  11. wbnews/import_export/backends/news.py +36 -0
  12. wbnews/import_export/handlers/__init__.py +1 -0
  13. wbnews/import_export/handlers/news.py +57 -0
  14. wbnews/import_export/parsers/__init__.py +0 -0
  15. wbnews/import_export/parsers/emails/__init__.py +0 -0
  16. wbnews/import_export/parsers/emails/news.py +39 -0
  17. wbnews/import_export/parsers/emails/utils.py +65 -0
  18. wbnews/import_export/parsers/rss/__init__.py +0 -0
  19. wbnews/import_export/parsers/rss/news.py +58 -0
  20. wbnews/locale/de/LC_MESSAGES/django.mo +0 -0
  21. wbnews/locale/de/LC_MESSAGES/django.po +166 -0
  22. wbnews/locale/de/LC_MESSAGES/django.po.translated +173 -0
  23. wbnews/locale/en/LC_MESSAGES/django.mo +0 -0
  24. wbnews/locale/en/LC_MESSAGES/django.po +159 -0
  25. wbnews/locale/fr/LC_MESSAGES/django.mo +0 -0
  26. wbnews/locale/fr/LC_MESSAGES/django.po +162 -0
  27. wbnews/migrations/0001_initial_squashed_0005_alter_news_import_source.py +349 -0
  28. wbnews/migrations/0006_alter_news_language.py +122 -0
  29. wbnews/migrations/0007_auto_20240103_0955.py +43 -0
  30. wbnews/migrations/0008_alter_news_language.py +123 -0
  31. wbnews/migrations/0009_newsrelationship_analysis_newsrelationship_sentiment.py +94 -0
  32. wbnews/migrations/0010_newsrelationship_important.py +17 -0
  33. wbnews/migrations/0011_newsrelationship_content_object_repr.py +18 -0
  34. wbnews/migrations/0012_alter_news_unique_together_news_identifier_and_more.py +91 -0
  35. wbnews/migrations/0013_alter_news_datetime.py +19 -0
  36. wbnews/migrations/0014_newsrelationship_unique_news_relationship.py +27 -0
  37. wbnews/migrations/__init__.py +0 -0
  38. wbnews/models/__init__.py +3 -0
  39. wbnews/models/llm/cleaned_news.py +66 -0
  40. wbnews/models/news.py +131 -0
  41. wbnews/models/relationships.py +45 -0
  42. wbnews/models/sources.py +73 -0
  43. wbnews/models/utils.py +15 -0
  44. wbnews/serializers.py +134 -0
  45. wbnews/signals.py +4 -0
  46. wbnews/tasks.py +16 -0
  47. wbnews/tests/__init__.py +0 -0
  48. wbnews/tests/conftest.py +6 -0
  49. wbnews/tests/parsers/__init__.py +0 -0
  50. wbnews/tests/parsers/test_emails.py +25 -0
  51. wbnews/tests/test_models.py +80 -0
  52. wbnews/tests/test_utils.py +7 -0
  53. wbnews/tests/tests.py +12 -0
  54. wbnews/urls.py +25 -0
  55. wbnews/utils.py +57 -0
  56. wbnews/viewsets/__init__.py +12 -0
  57. wbnews/viewsets/buttons.py +42 -0
  58. wbnews/viewsets/display.py +148 -0
  59. wbnews/viewsets/endpoints.py +34 -0
  60. wbnews/viewsets/menu.py +29 -0
  61. wbnews/viewsets/titles.py +44 -0
  62. wbnews/viewsets/views.py +168 -0
  63. wbnews-1.58.3.dist-info/METADATA +7 -0
  64. wbnews-1.58.3.dist-info/RECORD +65 -0
  65. wbnews-1.58.3.dist-info/WHEEL +5 -0
@@ -0,0 +1,36 @@
1
+ import json
2
+ from datetime import datetime
3
+ from io import BytesIO
4
+ from typing import Generator
5
+
6
+ import feedparser
7
+ from django.db.models import QuerySet
8
+ from slugify import slugify
9
+ from wbcore.contrib.io.backends.abstract import AbstractDataBackend
10
+ from wbcore.contrib.io.backends.utils import register
11
+
12
+ from wbnews.models import NewsSource
13
+
14
+
15
+ @register("News RSS Backend", save_data_in_import_source=True)
16
+ class DataBackend(AbstractDataBackend):
17
+ def is_object_valid(self, obj: "NewsSource") -> bool:
18
+ return obj.type == NewsSource.Type.RSS and obj.is_active and obj.endpoint
19
+
20
+ def get_default_queryset(self) -> QuerySet["NewsSource"]:
21
+ return NewsSource.objects.filter(type=NewsSource.Type.RSS, is_active=True, endpoint__isnull=False)
22
+
23
+ def get_files(
24
+ self, execution_time: datetime, queryset=None, **kwargs
25
+ ) -> Generator[tuple[str, BytesIO], None, None] | None:
26
+ if queryset is not None:
27
+ for source in queryset:
28
+ data = feedparser.parse(source.endpoint)
29
+ if not data.get("bozo_exception"):
30
+ data["news_source"] = source.id
31
+ content_file = BytesIO()
32
+ content_file.write(json.dumps(data).encode())
33
+ file_name = (
34
+ f"{slugify(source.title, separator='_')}_rss_file_{datetime.timestamp(execution_time)}.json"
35
+ )
36
+ yield file_name, content_file
@@ -0,0 +1 @@
1
+ from .news import NewsImportHandler
@@ -0,0 +1,57 @@
1
+ from datetime import datetime
2
+ from typing import TYPE_CHECKING, Any, Dict, Optional
3
+
4
+ import pytz
5
+ from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
6
+ from django.db import models
7
+ from django.utils import timezone
8
+ from slugify import slugify
9
+ from wbcore.contrib.io.imports import ImportExportHandler
10
+
11
+ if TYPE_CHECKING:
12
+ from wbnews.models import News
13
+
14
+
15
+ class NewsImportHandler(ImportExportHandler):
16
+ MODEL_APP_LABEL = "wbnews.News"
17
+ model: "News"
18
+
19
+ def _deserialize(self, data: Dict[str, Any]):
20
+ from wbnews.models.sources import NewsSource
21
+
22
+ data["source"] = NewsSource.source_dict_to_model(data["source"])
23
+ if parsed_datetime := data.get("datetime", None):
24
+ data["datetime"] = pytz.utc.localize(datetime.strptime(parsed_datetime, "%Y-%m-%dT%H:%M:%S"))
25
+ else:
26
+ data["datetime"] = timezone.now()
27
+
28
+ data["default_guid"] = self.model.get_default_guid(data["title"], data.get("link", None))
29
+ if guid := data.get("guid", None):
30
+ data["guid"] = slugify(guid)
31
+ else:
32
+ data["guid"] = data["default_guid"]
33
+
34
+ # constrained fields to the max allowed size
35
+ if "title" in data:
36
+ data["title"] = data["title"][:500]
37
+
38
+ if "link" in data:
39
+ data["link"] = data["link"][:1024]
40
+
41
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
42
+ default_guid = data.pop("default_guid")
43
+ instance = None
44
+ try:
45
+ instance = self.model.all_objects.get(models.Q(guid=data["guid"]) | models.Q(guid=default_guid))
46
+ except MultipleObjectsReturned:
47
+ instance = self.model.all_objects.get(models.Q(guid=data["guid"]))
48
+ except ObjectDoesNotExist:
49
+ pass
50
+
51
+ if instance:
52
+ self.import_source.log += f"\nFound existing news {instance.id} (guid: {instance.guid})"
53
+ return instance
54
+
55
+ def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
56
+ self.import_source.log += "\nCreate News."
57
+ return self.model.objects.create(**data, import_source=self.import_source)
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ import re
2
+ from contextlib import suppress
3
+ from datetime import datetime
4
+
5
+ from django.conf.global_settings import LANGUAGES
6
+ from wbcore.utils.importlib import import_from_dotted_path
7
+
8
+ from .utils import EmlContentParser
9
+
10
+ languages_dict = dict(LANGUAGES)
11
+
12
+
13
+ def clean_string_with_paragraphs(string):
14
+ return re.sub(r" +", " ", re.sub(r"(?<!\.)\\n", " ", string.strip()))
15
+
16
+
17
+ def parse(import_source):
18
+ parser = EmlContentParser(
19
+ import_source.file.read(), encoding=import_source.source.import_parameters.get("email_encoding", "latin-1")
20
+ )
21
+ email_date = parser.date if parser.date else datetime.now()
22
+
23
+ # Source
24
+ html = parser.html
25
+
26
+ # If source define a custom html parser, we import it and convert the returned html
27
+ if html_parser_path := import_source.source.import_parameters.get("html_parser", None):
28
+ with suppress(ModuleNotFoundError):
29
+ html_parser = import_from_dotted_path(html_parser_path)
30
+ html = html_parser(html)
31
+
32
+ data = {
33
+ "datetime": email_date.strftime("%Y-%m-%dT%H:%M:%S"),
34
+ "title": parser.subject.replace(f"[{import_source.source.uuid}]", ""),
35
+ "description": html,
36
+ "source": parser.source,
37
+ }
38
+
39
+ return {"data": [data]}
@@ -0,0 +1,65 @@
1
+ import re
2
+ from email import message, parser
3
+ from email.utils import parseaddr, parsedate_to_datetime
4
+
5
+
6
+ class EmlContentParser:
7
+ def __init__(self, email: bytes, encoding: str = "latin-1"):
8
+ self.message = parser.BytesParser().parsebytes(email)
9
+ self.encoding = encoding
10
+
11
+ @property
12
+ def date(self):
13
+ if date_str := self.message.get("date"):
14
+ return parsedate_to_datetime(date_str)
15
+
16
+ @property
17
+ def subject(self) -> str:
18
+ return self.message.get("subject", "")
19
+
20
+ @property
21
+ def html(self):
22
+ html = self.get_html(self.message)
23
+ return html.decode(self.encoding) if html else None
24
+
25
+ def get_html(self, parsed: message.Message) -> bytes | None:
26
+ if parsed.is_multipart():
27
+ for item in parsed.get_payload(): # type:message.Message
28
+ if html := self.get_html(item):
29
+ return html
30
+ elif parsed.get_content_type() == "text/html":
31
+ return parsed.get_payload(decode=True)
32
+ return None
33
+
34
+ @property
35
+ def text(self):
36
+ text = self.get_text(self.message)
37
+ return text.decode(self.encoding) if text else None
38
+
39
+ @classmethod
40
+ def get_text(cls, parsed: message.Message) -> bytes | None:
41
+ if parsed.is_multipart():
42
+ for item in parsed.get_payload():
43
+ if text := cls.get_text(item):
44
+ return text
45
+ elif parsed.get_content_type() == "text/plain":
46
+ return parsed.get_payload(decode=True)
47
+ return None
48
+
49
+ @property
50
+ def source(self) -> dict[str, any]:
51
+ match = re.search(r"From:(.*)<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>", self.text)
52
+ # we search first for forwarding info
53
+ if match:
54
+ name = match.group(1)
55
+ email = match.group(2)
56
+ else:
57
+ # otherwise we default to the From attribute
58
+ name, email = parseaddr(self.message["From"])
59
+
60
+ if not email:
61
+ raise ValueError("Couldn't find valid source data")
62
+ if not name:
63
+ name = email
64
+ source = {"title": name.split("@")[-1].strip().title(), "endpoint": email, "type": "EMAIL"}
65
+ return source
File without changes
@@ -0,0 +1,58 @@
1
+ import json
2
+ from datetime import datetime
3
+ from time import mktime
4
+
5
+ from django.conf.global_settings import LANGUAGES
6
+ from django.utils.html import strip_tags
7
+
8
+ languages_dict = dict(LANGUAGES)
9
+
10
+
11
+ def _get_source(d):
12
+ source = {"type": "RSS"}
13
+ if source_id := d.get("news_source"):
14
+ source["id"] = source_id
15
+ else:
16
+ if "title" in d["feed"]:
17
+ source["title"] = d["feed"]["title"]
18
+ if "author" in d["feed"]:
19
+ source["author"] = d["feed"]["author"]
20
+ if "image" in d["feed"]:
21
+ source["image"] = d["feed"]["image"]["href"]
22
+ if "href" in d["feed"]:
23
+ source["identifier"] = d["feed"]["href"]
24
+ if "link" in d["feed"]:
25
+ source["endpoint"] = d["feed"]["link"]
26
+ return source
27
+
28
+
29
+ def parse(import_source):
30
+ content = json.load(import_source.file)
31
+ data = []
32
+ source = _get_source(content)
33
+
34
+ for entry in content["entries"]:
35
+ if summary := entry.get("summary", None):
36
+ description = entry.get("description", summary)
37
+ res = {
38
+ "description": description,
39
+ "summary": summary,
40
+ "source": source,
41
+ "title": strip_tags(entry.get("title", "")).strip(),
42
+ "link": entry.get("link", None),
43
+ "guid": entry.get("id", None),
44
+ }
45
+ if published_parsed := entry.get("published_parsed", None):
46
+ updated = datetime.fromtimestamp(mktime(tuple(published_parsed)))
47
+ res["datetime"] = updated.strftime("%Y-%m-%dT%H:%M:%S")
48
+ if enclosures := entry.get("enclosures", None):
49
+ res["enclosures"] = [e.get("href", "") for e in enclosures]
50
+ if (
51
+ (media_content := entry.get("media_content", []))
52
+ and isinstance(media_content, list)
53
+ and len(media_content) > 0
54
+ and (image_url := media_content[0].get("url", None))
55
+ ):
56
+ res["image_url"] = image_url
57
+ data.append(res)
58
+ return {"data": data}
Binary file
@@ -0,0 +1,166 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ # Translators:
7
+ # Kevin Decoster, 2025
8
+ #
9
+ #, fuzzy
10
+ msgid ""
11
+ msgstr ""
12
+ "Project-Id-Version: PACKAGE VERSION\n"
13
+ "Report-Msgid-Bugs-To: \n"
14
+ "POT-Creation-Date: 2025-05-30 11:37+0200\n"
15
+ "PO-Revision-Date: 2025-05-30 09:40+0000\n"
16
+ "Last-Translator: Kevin Decoster, 2025\n"
17
+ "Language-Team: German (https://app.transifex.com/stainly/teams/171242/de/)\n"
18
+ "MIME-Version: 1.0\n"
19
+ "Content-Type: text/plain; charset=UTF-8\n"
20
+ "Content-Transfer-Encoding: 8bit\n"
21
+ "Language: de\n"
22
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
23
+
24
+ #: models/news.py:51 viewsets/display.py:54 viewsets/display.py:100
25
+ #: viewsets/display.py:141
26
+ msgid "Datetime"
27
+ msgstr "Zeitpunkt"
28
+
29
+ #: models/news.py:52 serializers.py:21 serializers.py:76
30
+ #: viewsets/display.py:20 viewsets/display.py:55 viewsets/display.py:102
31
+ #: viewsets/display.py:142
32
+ msgid "Title"
33
+ msgstr "Titel"
34
+
35
+ #: models/news.py:54 serializers.py:24 serializers.py:77
36
+ #: viewsets/display.py:23 viewsets/display.py:57 viewsets/display.py:104
37
+ #: viewsets/display.py:143
38
+ msgid "Description"
39
+ msgstr "Beschreibung"
40
+
41
+ #: models/news.py:55 serializers.py:78 viewsets/display.py:56
42
+ #: viewsets/display.py:103
43
+ msgid "Summary"
44
+ msgstr "Zusammenfassung"
45
+
46
+ #: models/news.py:56 viewsets/display.py:60 viewsets/display.py:144
47
+ msgid "Language"
48
+ msgstr "Sprache"
49
+
50
+ #: models/news.py:57
51
+ msgid "Link"
52
+ msgstr "Link"
53
+
54
+ #: models/news.py:61 viewsets/display.py:59 viewsets/display.py:106
55
+ msgid "Source"
56
+ msgstr "Quelle"
57
+
58
+ #: models/news.py:64
59
+ msgid "Mark as duplicate"
60
+ msgstr ""
61
+
62
+ #: models/relationships.py:9
63
+ msgid "Positive"
64
+ msgstr ""
65
+
66
+ #: models/relationships.py:10
67
+ msgid "Slightly Positive"
68
+ msgstr ""
69
+
70
+ #: models/relationships.py:11
71
+ msgid "Slightly Negative"
72
+ msgstr ""
73
+
74
+ #: models/relationships.py:12
75
+ msgid "Negative"
76
+ msgstr ""
77
+
78
+ #: serializers.py:22
79
+ msgid "Identifier"
80
+ msgstr "Identifikator"
81
+
82
+ #: serializers.py:25 viewsets/display.py:22
83
+ msgid "Author"
84
+ msgstr "Autor*in"
85
+
86
+ #: serializers.py:26
87
+ msgid "Updated"
88
+ msgstr "Geändert"
89
+
90
+ #: serializers.py:79
91
+ msgid "Date"
92
+ msgstr ""
93
+
94
+ #: serializers.py:92 viewsets/menu.py:6 viewsets/titles.py:24
95
+ msgid "News"
96
+ msgstr "Neuigkeiten"
97
+
98
+ #: viewsets/buttons.py:14
99
+ msgid "Open News"
100
+ msgstr "Neuigkeiten Öffnen"
101
+
102
+ #: viewsets/buttons.py:21 viewsets/buttons.py:22 viewsets/buttons.py:24
103
+ msgid "Reset relationships"
104
+ msgstr ""
105
+
106
+ #: viewsets/display.py:21
107
+ msgid "RSS feed"
108
+ msgstr "RSS Feed"
109
+
110
+ #: viewsets/display.py:24
111
+ msgid "Last Update"
112
+ msgstr "Letztes Aktualisierung"
113
+
114
+ #: viewsets/display.py:61
115
+ msgid "Image"
116
+ msgstr "Bild"
117
+
118
+ #: viewsets/display.py:93
119
+ msgid "Linked Object"
120
+ msgstr ""
121
+
122
+ #: viewsets/display.py:101
123
+ msgid "Analysis"
124
+ msgstr ""
125
+
126
+ #: viewsets/display.py:105
127
+ msgid "Important"
128
+ msgstr ""
129
+
130
+ #: viewsets/menu.py:11
131
+ msgid "News Relationships"
132
+ msgstr ""
133
+
134
+ #: viewsets/menu.py:17 viewsets/titles.py:9
135
+ msgid "Sources"
136
+ msgstr "Quellen"
137
+
138
+ #: viewsets/menu.py:23
139
+ msgid "Create Source"
140
+ msgstr "Quelle Erstellen"
141
+
142
+ #: viewsets/titles.py:13
143
+ #, python-brace-format
144
+ msgid "Source: {source}"
145
+ msgstr "Quelle: {source}"
146
+
147
+ #: viewsets/titles.py:14
148
+ msgid "News Source"
149
+ msgstr ""
150
+
151
+ #: viewsets/titles.py:19
152
+ msgid "News Flow"
153
+ msgstr "Nachrichtenfluss"
154
+
155
+ #: viewsets/titles.py:30
156
+ #, python-brace-format
157
+ msgid "News from {source}"
158
+ msgstr "Neuigkeiten von {source}"
159
+
160
+ #: viewsets/titles.py:36
161
+ msgid "News Article for {}"
162
+ msgstr ""
163
+
164
+ #: viewsets/titles.py:37 viewsets/titles.py:44
165
+ msgid "News Article"
166
+ msgstr ""
@@ -0,0 +1,173 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: \n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-05-29 13:33+0200\n"
11
+ "PO-Revision-Date: 2022-10-17 14:51+0200\n"
12
+ "Last-Translator: \n"
13
+ "Language-Team: \n"
14
+ "Language: de\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+ "X-Generator: Poedit 3.1.1\n"
20
+
21
+ #: wbnews/models/news.py:51 wbnews/viewsets/display.py:54
22
+ #: wbnews/viewsets/display.py:100 wbnews/viewsets/display.py:141
23
+ msgid "Datetime"
24
+ msgstr "Zeitpunkt"
25
+
26
+ #: wbnews/models/news.py:52 wbnews/serializers.py:21 wbnews/serializers.py:76
27
+ #: wbnews/viewsets/display.py:20 wbnews/viewsets/display.py:55
28
+ #: wbnews/viewsets/display.py:102 wbnews/viewsets/display.py:142
29
+ msgid "Title"
30
+ msgstr "Titel"
31
+
32
+ #: wbnews/models/news.py:54 wbnews/serializers.py:24 wbnews/serializers.py:77
33
+ #: wbnews/viewsets/display.py:23 wbnews/viewsets/display.py:57
34
+ #: wbnews/viewsets/display.py:104 wbnews/viewsets/display.py:143
35
+ msgid "Description"
36
+ msgstr "Beschreibung"
37
+
38
+ #: wbnews/models/news.py:55 wbnews/serializers.py:78
39
+ #: wbnews/viewsets/display.py:56 wbnews/viewsets/display.py:103
40
+ msgid "Summary"
41
+ msgstr "Zusammenfassung"
42
+
43
+ #: wbnews/models/news.py:56 wbnews/viewsets/display.py:60
44
+ #: wbnews/viewsets/display.py:144
45
+ msgid "Language"
46
+ msgstr "Sprache"
47
+
48
+ #: wbnews/models/news.py:57
49
+ msgid "Link"
50
+ msgstr "Link"
51
+
52
+ #: wbnews/models/news.py:61 wbnews/viewsets/display.py:59
53
+ #: wbnews/viewsets/display.py:106
54
+ msgid "Source"
55
+ msgstr "Quelle"
56
+
57
+ #: wbnews/models/news.py:64
58
+ msgid "Mark as duplicate"
59
+ msgstr ""
60
+
61
+ #: wbnews/models/relationships.py:9
62
+ msgid "Positive"
63
+ msgstr ""
64
+
65
+ #: wbnews/models/relationships.py:10
66
+ msgid "Slightly Positive"
67
+ msgstr ""
68
+
69
+ #: wbnews/models/relationships.py:11
70
+ msgid "Slightly Negative"
71
+ msgstr ""
72
+
73
+ #: wbnews/models/relationships.py:12
74
+ msgid "Negative"
75
+ msgstr ""
76
+
77
+ #: wbnews/serializers.py:22
78
+ msgid "Identifier"
79
+ msgstr "Identifikator"
80
+
81
+ #: wbnews/serializers.py:25 wbnews/viewsets/display.py:22
82
+ msgid "Author"
83
+ msgstr "Autor*in"
84
+
85
+ #: wbnews/serializers.py:26
86
+ msgid "Updated"
87
+ msgstr "Geändert"
88
+
89
+ #: wbnews/serializers.py:79
90
+ #, fuzzy
91
+ #| msgid "Datetime"
92
+ msgid "Date"
93
+ msgstr "Zeitpunkt"
94
+
95
+ #: wbnews/serializers.py:92 wbnews/viewsets/menu.py:6
96
+ #: wbnews/viewsets/titles.py:24 wbnews_config/menu.py:5
97
+ msgid "News"
98
+ msgstr "Neuigkeiten"
99
+
100
+ #: wbnews/viewsets/buttons.py:14
101
+ msgid "Open News"
102
+ msgstr "Neuigkeiten Öffnen"
103
+
104
+ #: wbnews/viewsets/buttons.py:21 wbnews/viewsets/buttons.py:22
105
+ #: wbnews/viewsets/buttons.py:24
106
+ msgid "Reset relationships"
107
+ msgstr ""
108
+
109
+ #: wbnews/viewsets/display.py:21
110
+ msgid "RSS feed"
111
+ msgstr "RSS Feed"
112
+
113
+ #: wbnews/viewsets/display.py:24
114
+ msgid "Last Update"
115
+ msgstr "Letztes Aktualisierung"
116
+
117
+ #: wbnews/viewsets/display.py:61
118
+ msgid "Image"
119
+ msgstr "Bild"
120
+
121
+ #: wbnews/viewsets/display.py:93
122
+ msgid "Linked Object"
123
+ msgstr ""
124
+
125
+ #: wbnews/viewsets/display.py:101
126
+ msgid "Analysis"
127
+ msgstr ""
128
+
129
+ #: wbnews/viewsets/display.py:105
130
+ msgid "Important"
131
+ msgstr ""
132
+
133
+ #: wbnews/viewsets/menu.py:11
134
+ msgid "News Relationships"
135
+ msgstr ""
136
+
137
+ #: wbnews/viewsets/menu.py:17 wbnews/viewsets/titles.py:9
138
+ msgid "Sources"
139
+ msgstr "Quellen"
140
+
141
+ #: wbnews/viewsets/menu.py:23
142
+ msgid "Create Source"
143
+ msgstr "Quelle Erstellen"
144
+
145
+ #: wbnews/viewsets/titles.py:13
146
+ #, python-brace-format
147
+ msgid "Source: {source}"
148
+ msgstr "Quelle: {source}"
149
+
150
+ #: wbnews/viewsets/titles.py:14
151
+ #, fuzzy
152
+ #| msgid "Source"
153
+ msgid "News Source"
154
+ msgstr "Quelle"
155
+
156
+ #: wbnews/viewsets/titles.py:19
157
+ msgid "News Flow"
158
+ msgstr "Nachrichtenfluss"
159
+
160
+ #: wbnews/viewsets/titles.py:30
161
+ #, python-brace-format
162
+ msgid "News from {source}"
163
+ msgstr "Neuigkeiten von {source}"
164
+
165
+ #: wbnews/viewsets/titles.py:36
166
+ msgid "News Article for {}"
167
+ msgstr ""
168
+
169
+ #: wbnews/viewsets/titles.py:37 wbnews/viewsets/titles.py:44
170
+ #, fuzzy
171
+ #| msgid "Source"
172
+ msgid "News Article"
173
+ msgstr "Quelle"
Binary file