diff --git a/fgs/__main__.py b/fgs/__main__.py
index 03ac928cedde8975293da8dd1e8abbfebea196ed..266388e986c763587c95a15faae043696f7cc210 100644
--- a/fgs/__main__.py
+++ b/fgs/__main__.py
@@ -27,8 +27,10 @@ def main():
     print(config)
 
     factories = {}
-    factories['page'] = datatypes.PageFactory()
-    factories['author'] = datatypes.AuthorFactory()
+    factories['page'] = datatypes.PageFactory(config, factories)
+    factories['author'] = datatypes.AuthorFactory(config, factories)
+    factories['tag'] = datatypes.TagFactory(config, factories)
+    factories['link'] = datatypes.LinkFactory(config, factories)
 
     mdreader = reader.MarkdownReader(config, factories)
     pages = []
@@ -48,7 +50,7 @@ def main():
 
 
     context = {}
-    gen = generator.Generator(config, context)
+    gen = generator.Generator(config, context, factories)
     gen.generate_context(pages, static_files)
 
     wrt = writer.Writer(config, context, OUTPUT_DIR, THEME_DIR)
diff --git a/fgs/datatypes.py b/fgs/datatypes.py
index 3da0bb197ff5780d2d5bd584f09b09138f9d1b7b..71605d74cc644d9bc03fe7ed247c1ff1a731b515 100644
--- a/fgs/datatypes.py
+++ b/fgs/datatypes.py
@@ -2,105 +2,126 @@ import datetime
 from dateutil import parser as dtparser
 
 class PageFactory:
-    def __init__(self):
+    def __init__(self, config, factories):
+        self.config = config
+        self.factories = factories
         self.store = {}
+        for lang in self.config['lang']['supported']:
+            self.store[lang] = {}
+
+    def has(self, slug, lang):
+        return (slug in self.store[lang])
+
     def get(self, slug, lang):
-        if slug not in self.store:
-            self.store[slug] = {}
-        if lang not in self.store[slug]:
-            self.store[slug][lang] = Page(slug, lang)
-        return self.store[slug][lang];
+        if not self.has(slug, lang):
+            self.store[lang][slug] = Page(slug, lang, self.config, self.factories)
+        return self.store[lang][slug];
 class Page:
-    def __init__(self, slug, lang):
+    def __init__(self, slug, lang, config, factories):
         self.slug = slug
         self.lang = lang
+        self._config = config
+        self._factories = factories
         self.initialized = False
 
-    def init(self, filename, subpath, raw, metadata, content, title, category, date_created, date_modified, authors, last_modification_author, status, config, factories):
+    def init(self, filename, subpath, raw, metadata, content, title, category, date_created, date_modified, authors, last_modification_author, status):
         self.initialized = True
         self.filename =  filename
         self.subpath =  subpath
         self.raw =  raw
-        self.metadata =  metadata
+        self._metadata =  metadata
         self.content =  content
         self.title =  title
-        self.category =  category
-        self.date_created =  Date(date_created, config)
-        self.date_modified =  Date(date_modified, config)
+        self.category = self._factories['tag'].get(category, self.lang)
+        self.date_created =  Date(date_created, self._config)
+        self.date_modified =  Date(date_modified, self._config)
         self.status =  status
 
-        #self.config = config
-
-
         # authors
         self.authors = []
         for local_part, domain, name in authors:
-            self.authors.append(factories['author'].get(local_part + '@' + domain, name))
+            self.authors.append(self._factories['author'].get(local_part + '@' + domain, name))
         if last_modification_author:
-            self.last_modification_author = factories['author'].get(last_modification_author[0] + '@' + last_modification_author[1], last_modification_author[2])
+            self.last_modification_author = self._factories['author'].get(last_modification_author[0] + '@' + last_modification_author[1], last_modification_author[2])
         else:
             self.last_modification_author = None
         #print(authors)
         #print(last_modification_author)
 
+        # category
+        self.category.reg_page(self, True)
+
+        # url
+        #self.url = self.lang + '/' + self.category.name + '/' + self.slug + ".html"
+        self.link = self._factories['link'].get_by_type("slug", self.slug, self.lang)
 
-        # tags
-        self.tags = []
-        if 'tags' in metadata:
-            tags = self.tags_str_to_list(metadata['tags'])
-            for t in tags:
-                if (t != category):
-                    self.tags.append(t)
-        self.tags.append(self.category) # the category is also a default tag
 
-        # template
-        if 'template' in metadata:
-            self.template = metadata['template']
-        else:
-            self.template = config['theme']['default_template']
+    def get_metadata(self):
+        deflang = self._config['lang']['default']
+        if self.lang == deflang:
+            return self._metadata
+        defpage = self._factories['page'].get(self.slug, deflang)
+        res = defpage.metadata.copy()
+        res.update(self._metadata)
+        return res
+    metadata = property(get_metadata, None, None)
 
-        # url
-        self.url = self.lang + '/' + self.category + '/' + self.slug + ".html"
-
-        # relevance
-        self.relevance = {}
-        self.relevance['is_relevant'] = False
-        self.relevance['was_relevant'] = False
-        self.relevance['will_be_relevant'] = False
-        if 'relevant' in metadata:
-            lrel = metadata['relevant']
+    def get_tags(self):
+        res = []
+        if 'tags' in self.metadata:
+            rawtags = self.tags_str_to_list(self.metadata['tags'])
+            for t in rawtags:
+                if (t != self.category.name):
+                    tag = factories['tag'].get(t, self.lang)
+                    tag.reg_page(self)
+                    res.append(tag)
+        res.append(self.category) # the category is also a default tag
+        return res
+    tags = property(get_tags, None, None)
+
+    def get_relevance(self):
+        res = {}
+        res['is_relevant'] = False
+        res['was_relevant'] = False
+        res['will_be_relevant'] = False
+        if 'relevant' in self.metadata:
+            lrel = self.metadata['relevant']
             if not isinstance(lrel, dict):
                 prio = lrel
                 lrel = {}
                 lrel['prio'] = prio
 
             if 'prio' in lrel:
-                self.relevance['prio']= lrel['prio']
+                res['prio']= lrel['prio']
             else:
-                self.relevance['prio']= 0
+                res['prio']= 0
 
-            start = Date("min", config)
+            start = Date("min", self._config)
             if 'start' in lrel:
-                start = Date(lrel['start'], config)
-            self.relevance['start'] = start
+                start = Date(lrel['start'], self._config)
+            res['start'] = start
 
-            end = Date("max", config)
+            end = Date("max", self._config)
             if 'end' in lrel:
-                end = Date(lrel['end'], config)
-            self.relevance['end'] = end
+                end = Date(lrel['end'], self._config)
+            res['end'] = end
 
-            build_date = Date("now", config)
+            build_date = Date("now", self._config)
             if (build_date > end):
-                self.relevance['was_relevant'] = True
+                res['was_relevant'] = True
             elif (build_date < start):
-                self.relevance['will_be_relevant'] = True
+                res['will_be_relevant'] = True
             else:
-                self.relevance['is_relevant'] = True
-
-
-
-
+                res['is_relevant'] = True
+        return res
+    relevance = property(get_relevance, None, None)
 
+    def get_template(self):
+        if 'template' in self.metadata:
+            return self.metadata['template']
+        else:
+            return self._config['theme']['default_template']
+    template = property(get_template, None, None)
 
 
     def tags_str_to_list(self, s):
@@ -123,6 +144,66 @@ class Page:
             raise Exception("Page not initialized.")
         return self.date_modified < other.date_modified
 
+class TagFactory:
+    def __init__(self, config, factories):
+        self.config = config
+        self.factories = factories
+        self.store = {}
+        for lang in self.config['lang']['supported']:
+            self.store[lang] = {}
+    def get(self, name, lang):
+        if not isinstance(name, str):
+            raise Exception("name is not a string", name, type(name))
+        if name not in self.store[lang]:
+            self.store[lang][name] = Tag(name, lang, self.factories)
+        return self.store[lang][name];
+    def all(self):
+        return self.store
+class Tag:
+    def __init__(self, name, lang, factories):
+        self.name = name
+        self.lang = lang
+        self.pages = set()
+        self.is_category = False
+        self._link = None
+        self._factories = factories
+
+
+    def get_link(self):
+        if not self._link:
+            self._link = self._factories['link'].get_by_type("tag", self.name, self.lang)
+        return self._link
+    link = property(get_link, None, None)
+
+    def get_page(self):
+        if self._factories['page'].has('tag-' + self.name, self.lang):
+            return self._factories['page'].get('tag-' + self.name, self.lang)
+        else:
+            return None
+    page = property(get_page, None, None)
+
+    def get_color(self):
+        if 'color' in self.page.metadata:
+            return self.page.metadata['color']
+        else:
+            return self.config['default_tag_color']
+    color = property(get_color, None, None)
+
+    def get_title(self):
+        if self.page:
+            return self.page.title
+        else:
+            return self.name
+    title = property(get_title, None, None)
+
+    def reg_page(self, page, is_category = False):
+        if page == None:
+            return
+        if page.lang != self.lang:
+            raise Exception("Page has different lang than Tag.", page.lang, tag.lang)
+        self.pages.add(page)
+        if is_category:
+            self.is_category = is_category
 
 
 class Date:
@@ -148,17 +229,20 @@ class Date:
         return self.dt.timestamp() < other.dt.timestamp()
 
 class AuthorFactory:
-    def __init__(self):
+    def __init__(self, config, factories):
+        self.config = config
+        self.factories = factories
         self.store = {}
     def get(self, email, name = None):
         if email not in self.store:
-            self.store[email] = Author(email, name)
-        return self.store[email];
+            self.store[email] = Author(email)
+        author = self.store[email]
+        author.reg_name(name)
+        return author;
 class Author:
-    def __init__(self, email, name = None):
+    def __init__(self, email):
         self.email = email
         self.names = set()
-        self.reg_name(name)
 
         split = email.split('@')
         # TODO proper email validation
@@ -172,4 +256,124 @@ class Author:
         if name == None:
             return
         self.names.add(name)
+
+class LinkFactory:
+    def __init__(self, config, factories):
+        self.config = config
+        self.factories = factories
+        self.store = {}
+    def get_by_raw(self, rawurl, deflang):
+        if rawurl not in self.store:
+            self.store[rawurl] = Link(rawurl, self.factories, deflang)
+        return self.store[rawurl];
+    def get_by_type(self, reftype, refid, reflang):
+        rawurl = reftype + ':' + refid + ':' + reflang
+        return self.get_by_raw(rawurl=rawurl, deflang=reflang)
+class Link:
+    def __init__(self, rawurl, factories, deflang):
+        self.raw = rawurl
+        self.is_external = False
+
+        urlasplit = rawurl.split('#')
+
+        self.anchor = None
+        if len(urlasplit) == 2:
+            self.anchor = urlasplit[1]
+
+        urlwoa = urlasplit[0] # url without anchor
+        if len(urlwoa) == 0:
+            # rawurl: '#some-section'
+            self.url = ""
+            self.type = "local"
+        else:
+            components = urlwoa.split(':')
+            if len(components) == 1:
+                # rawurl: 'lageplan.uni-goettingen.de'
+                self.url = "https://" + urlwoa
+                self.is_external = True
+            else:
+                reftype = components[0]
+                refid = components[1]
+                reflang = deflang
+                if len(components) > 2:
+                    reflang = components[2]
+                if reftype == "slug":
+                    # rawurl: 'slug:some-page:de'
+                    self.page = factories['page'].get(refid, reflang)
+                    # TODO handle link/alias pages
+                    self.path = '/'.join([self.page.lang, self.page.category.name, self.page.slug + '.html'])
+                    self.url = self.path
+                    self.type = reftype
+                elif reftype == "tag":
+                    # rawurl: 'tag:some-tag:de'
+                    self.tag = factories['tag'].get(refid, reflang)
+                    self.path = '/'.join([self.tag.lang, 'tag', self.tag.name + '.html'])
+                    self.url = self.path
+                    self.type = reftype
+                else:
+                    # rawurl: 'https://example.com'
+                    self.url = urlwoa
+                    self.is_external = True
+
+        self.urlwithanchor = self.url
+        if self.anchor:
+            self.urlwithanchor = self.url + "#" + self.anchor
+
+
+
+
+
+
+    
+        
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
diff --git a/fgs/generator.py b/fgs/generator.py
index ce6ee602fa24507f8928c2e2442520863fe1579c..6115044a7eb012e45a3fd760bb7ccd69e1791a00 100644
--- a/fgs/generator.py
+++ b/fgs/generator.py
@@ -1,9 +1,10 @@
 import datatypes
 
 class Generator:
-    def __init__(self, config, context):
+    def __init__(self, config, context, factories):
         self.config = config
         self.context = context
+        self.factories = factories
 
     def generate_context(self, pages, static_files):
         # static_files
@@ -41,26 +42,17 @@ class Generator:
         # TODO draft pages
         # TODO authors
 
-        # categories
-        categories = {}
-        for page in published_pages:
-            if page.lang not in categories:
-                categories[page.lang] = {}
-            if page.category not in categories[page.lang]:
-                categories[page.lang][page.category] = []
-            categories[page.lang][page.category].append(page)
-        self.context['categories'] = categories
 
         # tags
-        tags = {}
-        for page in published_pages:
-            for tag in page.tags:
-                if page.lang not in tags:
-                    tags[page.lang] = {}
-                if tag not in tags[page.lang]:
-                    tags[page.lang][tag] = []
-                tags[page.lang][tag].append(page)
-        self.context['tags'] = tags
+        self.context['tags'] = self.factories['tag'].all()
+
+        # categories
+        self.context['categories'] = {}
+        for lang in self.config['lang']['supported']:
+            self.context['categories'][lang] = {}
+            for tag in self.context['tags'][lang].values():
+                if tag.is_category:
+                    self.context['categories'][lang][tag.name] = tag
 
         # build_date
         self.context['build_date'] = datatypes.Date("now", self.config)
@@ -96,11 +88,11 @@ class Generator:
         for lang in self.config['lang']['supported']:
             # all pages
             for page in self.context['pages'][lang].values():
-                writer.write_template(page.template, page.url, lang, {'page': page})
+                writer.write_template(page.template, page.link.path, lang, {'page': page})
 
             # all tags
-            for tag, tagpages in self.context['tags'][lang].items():
-                writer.write_template('tag.html', lang + '/tag/' + tag + '.html', lang, {'tag': tag, 'tagpages': tagpages})
+            for tag in self.context['tags'][lang].values():
+                writer.write_template('tag.html', tag.link.path, lang, {'tag': tag})
 
             # homepages for languages
             self.generate_homepage(writer, lang, lang + "/index.html")
diff --git a/fgs/reader.py b/fgs/reader.py
index 3bf9d51bb9d76050cc5487eaeffa2bf9428e152e..a800024e01d26071895f78765b9a900c0b9d11ae 100644
--- a/fgs/reader.py
+++ b/fgs/reader.py
@@ -125,9 +125,7 @@ class MarkdownReader:
                 date_modified,
                 authors,
                 last_modification_author,
-                status,
-                self.config,
-                self.factories)
+                status)
         return p
 
     def extract_author(self, raw):
diff --git a/fgs/writer.py b/fgs/writer.py
index a5eb1e6098b5f427b24e4e72d6ad7eddd6e26d25..314acfc9471d457c117aa02669df7ea2f000fce5 100644
--- a/fgs/writer.py
+++ b/fgs/writer.py
@@ -43,6 +43,12 @@ class Writer:
         context["path"] = path
         context["siteurl"] = siteurl
         context.update(extra_context)
+        print("template: ", template)
+        print("path: ", path)
+        print("lang: ", lang)
+        print("context: ", context)
+        print("context keys: ", context.keys())
+        print("tags:", type(context['tags']), context['tags'])
         out = tmpl.render(context)
 
         # write file