sampledoc 0.1.0__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.
- sampledoc/__init__.py +89 -0
- sampledoc/__main__.py +5 -0
- sampledoc/_language.py +37 -0
- sampledoc/_table.py +109 -0
- sampledoc/cli.py +90 -0
- sampledoc/languages/cs.json +209 -0
- sampledoc/languages/de.json +209 -0
- sampledoc/languages/en.json +209 -0
- sampledoc/languages/es.json +209 -0
- sampledoc/languages/et.json +209 -0
- sampledoc/languages/fr.json +209 -0
- sampledoc/languages/hi.json +209 -0
- sampledoc/languages/it.json +209 -0
- sampledoc/languages/ja.json +210 -0
- sampledoc/languages/pl.json +209 -0
- sampledoc/languages/pt-BR.json +209 -0
- sampledoc/languages/ru.json +209 -0
- sampledoc/languages/sv.json +209 -0
- sampledoc/languages/tr.json +209 -0
- sampledoc/languages/zh.json +210 -0
- sampledoc/names.py +112 -0
- sampledoc/odf.py +267 -0
- sampledoc/ooxml.py +257 -0
- sampledoc/pdf.py +223 -0
- sampledoc/plain.py +37 -0
- sampledoc/py.typed +0 -0
- sampledoc-0.1.0.dist-info/METADATA +149 -0
- sampledoc-0.1.0.dist-info/RECORD +31 -0
- sampledoc-0.1.0.dist-info/WHEEL +4 -0
- sampledoc-0.1.0.dist-info/entry_points.txt +2 -0
- sampledoc-0.1.0.dist-info/licenses/LICENSE +373 -0
sampledoc/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Small, localized, reproducible sample office documents.
|
|
2
|
+
|
|
3
|
+
>>> import sampledoc
|
|
4
|
+
>>> sampledoc.languages()
|
|
5
|
+
('en', 'cs', 'de', ...)
|
|
6
|
+
>>> data = sampledoc.document("text", "de") # the German report, as .odt bytes
|
|
7
|
+
>>> sampledoc.generate("out/", languages=["en"]) # every sample, written to disk
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable, Iterable
|
|
13
|
+
from os import PathLike
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import NamedTuple
|
|
16
|
+
|
|
17
|
+
from . import _language, odf, ooxml, pdf, plain
|
|
18
|
+
from ._language import languages, load
|
|
19
|
+
from .names import file_names, manifest, query
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"SAMPLES",
|
|
23
|
+
"Sample",
|
|
24
|
+
"document",
|
|
25
|
+
"file_name",
|
|
26
|
+
"file_names",
|
|
27
|
+
"generate",
|
|
28
|
+
"languages",
|
|
29
|
+
"manifest",
|
|
30
|
+
"query",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.0"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Sample(NamedTuple):
|
|
37
|
+
extension: str
|
|
38
|
+
build: Callable[[dict], bytes]
|
|
39
|
+
description: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Every kind of sample, by the name it is written under. The first three are the
|
|
43
|
+
# documents the screenshots open; the rest fill the folder around them.
|
|
44
|
+
SAMPLES: dict[str, Sample] = {
|
|
45
|
+
"text": Sample("odt", odf.odt, "a quarterly report with a table of costs"),
|
|
46
|
+
"sheet": Sample("ods", odf.ods, "a budget over two sheets"),
|
|
47
|
+
"slides": Sample("odp", odf.odp, "a three slide project plan"),
|
|
48
|
+
"word": Sample("docx", ooxml.docx, "a service agreement with an appendix"),
|
|
49
|
+
"cells": Sample("xlsx", ooxml.xlsx, "a small table of sales figures"),
|
|
50
|
+
"deck": Sample("pptx", ooxml.pptx, "a single titled, bulleted slide"),
|
|
51
|
+
"paper": Sample("pdf", pdf.pdf, "an invoice over two pages"),
|
|
52
|
+
"rows": Sample("csv", plain.csv, "a contact list"),
|
|
53
|
+
"notes": Sample("txt", plain.txt, "the report as plain text notes"),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def file_name(sample: str, language: str) -> str:
|
|
58
|
+
"""The name a sample is written under, e.g. ``sample-text-de.odt``."""
|
|
59
|
+
return f"sample-{sample}-{language}.{SAMPLES[sample].extension}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def document(sample: str, language: str = "en") -> bytes:
|
|
63
|
+
"""One sample, as the bytes of its file. The same arguments give the same bytes."""
|
|
64
|
+
if sample not in SAMPLES:
|
|
65
|
+
raise KeyError(f"no sample called {sample!r}; there are {', '.join(SAMPLES)}")
|
|
66
|
+
|
|
67
|
+
return SAMPLES[sample].build(load(language))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def generate(
|
|
71
|
+
directory: str | PathLike,
|
|
72
|
+
languages: Iterable[str] | None = None,
|
|
73
|
+
samples: Iterable[str] | None = None,
|
|
74
|
+
) -> list[Path]:
|
|
75
|
+
"""Write samples into `directory`, creating it if need be.
|
|
76
|
+
|
|
77
|
+
Every sample in every language unless narrowed down. Returns the paths written.
|
|
78
|
+
"""
|
|
79
|
+
target = Path(directory)
|
|
80
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
written = []
|
|
83
|
+
for language in languages or _language.languages():
|
|
84
|
+
for sample in samples or SAMPLES:
|
|
85
|
+
path = target / file_name(sample, language)
|
|
86
|
+
path.write_bytes(document(sample, language))
|
|
87
|
+
written.append(path)
|
|
88
|
+
|
|
89
|
+
return written
|
sampledoc/__main__.py
ADDED
sampledoc/_language.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The wording of the samples, one JSON file per language under `languages/`.
|
|
2
|
+
|
|
3
|
+
Adding a language is adding a file: copy `en.json`, translate the strings, keep
|
|
4
|
+
the keys. Nothing else in the package lists the languages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from functools import cache
|
|
11
|
+
from importlib import resources
|
|
12
|
+
|
|
13
|
+
FALLBACK = "en"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cache
|
|
17
|
+
def languages() -> tuple[str, ...]:
|
|
18
|
+
"""Every language there is a file for, English first and the rest by code."""
|
|
19
|
+
found = sorted(
|
|
20
|
+
entry.name.removesuffix(".json")
|
|
21
|
+
for entry in resources.files(__package__).joinpath("languages").iterdir()
|
|
22
|
+
if entry.name.endswith(".json")
|
|
23
|
+
)
|
|
24
|
+
found.remove(FALLBACK)
|
|
25
|
+
|
|
26
|
+
return (FALLBACK, *found)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cache
|
|
30
|
+
def load(language: str) -> dict:
|
|
31
|
+
"""The wording for one language, as written in its file."""
|
|
32
|
+
if language not in languages():
|
|
33
|
+
raise KeyError(f"no samples written in {language!r}; there are {', '.join(languages())}")
|
|
34
|
+
|
|
35
|
+
text = resources.files(__package__).joinpath("languages", f"{language}.json").read_text("utf-8")
|
|
36
|
+
|
|
37
|
+
return json.loads(text)
|
sampledoc/_table.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""The figures every sample that has a table takes its numbers from."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import zipfile
|
|
7
|
+
|
|
8
|
+
# 1980-01-01, what zip stores when it is given nothing: a rerun with the same
|
|
9
|
+
# words has to produce the same bytes, or every run is a commit
|
|
10
|
+
EPOCH = (1980, 1, 1, 0, 0, 0)
|
|
11
|
+
|
|
12
|
+
# One accent, used for the report's headings and the slide titles, so the
|
|
13
|
+
# documents read as one set.
|
|
14
|
+
ACCENT = "#1c6fd6"
|
|
15
|
+
RULE = "#d4d9e0"
|
|
16
|
+
|
|
17
|
+
# What the sheets add up: forty rows over six periods, so there is enough of it
|
|
18
|
+
# to look like a spreadsheet. The first twenty keep the first four figures they
|
|
19
|
+
# had, because the invoice and the .xlsx take slices off the front.
|
|
20
|
+
FIGURES = [
|
|
21
|
+
[1200, 1450, 1310, 1600, 1380, 1520],
|
|
22
|
+
[480, 620, 510, 470, 690, 540],
|
|
23
|
+
[3600, 900, 1200, 750, 830, 1150],
|
|
24
|
+
[820, 760, 930, 1010, 870, 940],
|
|
25
|
+
[540, 560, 580, 600, 610, 630],
|
|
26
|
+
[300, 450, 380, 520, 410, 470],
|
|
27
|
+
[1100, 1150, 1180, 1240, 1260, 1290],
|
|
28
|
+
[640, 640, 660, 680, 700, 700],
|
|
29
|
+
[420, 980, 350, 610, 1240, 380],
|
|
30
|
+
[260, 280, 270, 300, 290, 310],
|
|
31
|
+
[890, 910, 940, 980, 1000, 1030],
|
|
32
|
+
[1500, 400, 620, 350, 480, 390],
|
|
33
|
+
[340, 360, 350, 370, 380, 390],
|
|
34
|
+
[220, 220, 230, 230, 240, 240],
|
|
35
|
+
[180, 620, 210, 240, 190, 660],
|
|
36
|
+
[410, 430, 400, 450, 440, 460],
|
|
37
|
+
[760, 820, 690, 900, 850, 780],
|
|
38
|
+
[950, 480, 1100, 520, 560, 1180],
|
|
39
|
+
[280, 290, 300, 310, 320, 330],
|
|
40
|
+
[520, 540, 530, 560, 570, 590],
|
|
41
|
+
[1340, 1290, 1410, 1360, 1440, 1480],
|
|
42
|
+
[710, 730, 720, 750, 760, 780],
|
|
43
|
+
[190, 200, 190, 210, 200, 220],
|
|
44
|
+
[330, 330, 340, 340, 350, 350],
|
|
45
|
+
[860, 890, 1240, 910, 930, 960],
|
|
46
|
+
[240, 250, 260, 260, 270, 280],
|
|
47
|
+
[120, 130, 120, 140, 130, 150],
|
|
48
|
+
[460, 170, 180, 490, 180, 190],
|
|
49
|
+
[580, 610, 550, 640, 600, 670],
|
|
50
|
+
[150, 160, 170, 160, 180, 170],
|
|
51
|
+
[210, 230, 220, 250, 240, 260],
|
|
52
|
+
[390, 390, 400, 410, 410, 420],
|
|
53
|
+
[270, 1080, 310, 340, 290, 360],
|
|
54
|
+
[1620, 350, 380, 360, 400, 370],
|
|
55
|
+
[620, 650, 630, 680, 660, 700],
|
|
56
|
+
[110, 120, 110, 130, 120, 140],
|
|
57
|
+
[440, 450, 460, 470, 480, 490],
|
|
58
|
+
[980, 1020, 640, 1060, 1090, 720],
|
|
59
|
+
[560, 500, 590, 530, 610, 570],
|
|
60
|
+
[740, 770, 800, 790, 830, 860],
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def table(words: dict, columns: int = 4, rows: int = 0, scale: int = 1) -> tuple:
|
|
65
|
+
"""The figures as rows of cells: a header, the items across as many periods
|
|
66
|
+
as are asked for with their totals, and a totals row under them.
|
|
67
|
+
|
|
68
|
+
Narrowed and shortened for the files that are not the budget, so the .xlsx
|
|
69
|
+
and the invoice hold their own figures rather than the .ods twice.
|
|
70
|
+
|
|
71
|
+
As wide as the header a language has words for, so one translated ahead of
|
|
72
|
+
the others comes out short rather than out of step.
|
|
73
|
+
"""
|
|
74
|
+
periods = words["periods"][:columns]
|
|
75
|
+
columns = len(periods)
|
|
76
|
+
taken = FIGURES[:rows] if rows else FIGURES
|
|
77
|
+
names = words["rows"][: len(taken)]
|
|
78
|
+
|
|
79
|
+
head = [words["item"]] + periods + [words["total"]]
|
|
80
|
+
body = [
|
|
81
|
+
[name] + [value * scale for value in figures[:columns]] + [sum(figures[:columns]) * scale]
|
|
82
|
+
for name, figures in zip(names, taken, strict=False)
|
|
83
|
+
]
|
|
84
|
+
foot = (
|
|
85
|
+
[words["total"]]
|
|
86
|
+
+ [sum(line[column + 1] for line in body) for column in range(len(periods))]
|
|
87
|
+
+ [sum(line[-1] for line in body)]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return head, body, foot
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def zipped(parts: list[tuple[str, str, bool]]) -> bytes:
|
|
94
|
+
"""A zip of (name, text, stored) parts in the order given, reproducibly.
|
|
95
|
+
|
|
96
|
+
Every entry carries the same date, permissions and creating system, so the
|
|
97
|
+
archive depends on nothing but its contents - not the clock, not the platform.
|
|
98
|
+
"""
|
|
99
|
+
out = io.BytesIO()
|
|
100
|
+
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as archive:
|
|
101
|
+
for name, text, stored in parts:
|
|
102
|
+
info = zipfile.ZipInfo(name, date_time=EPOCH)
|
|
103
|
+
info.external_attr = 0o644 << 16
|
|
104
|
+
# ZipInfo records the OS it was made on (0 on Windows, 3 elsewhere),
|
|
105
|
+
# and that byte would make the same samples differ by platform
|
|
106
|
+
info.create_system = 3
|
|
107
|
+
archive.writestr(info, text, compress_type=zipfile.ZIP_STORED if stored else zipfile.ZIP_DEFLATED)
|
|
108
|
+
|
|
109
|
+
return out.getvalue()
|
sampledoc/cli.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""sampledoc [-o DIR] [-l LANG]... [-s SAMPLE]... [--locales FILE] [--no-names]"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import SAMPLES, __version__, file_name, generate, languages, manifest
|
|
10
|
+
|
|
11
|
+
NAMES_FILE = "screenshot-names.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: list[str] | None = None) -> int:
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="sampledoc",
|
|
17
|
+
description="Write small, localized, reproducible sample office documents.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"-o",
|
|
22
|
+
"--output",
|
|
23
|
+
type=Path,
|
|
24
|
+
default=Path("."),
|
|
25
|
+
metavar="DIR",
|
|
26
|
+
help="where to write the samples (default: the current directory)",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"-l",
|
|
30
|
+
"--language",
|
|
31
|
+
action="append",
|
|
32
|
+
choices=languages(),
|
|
33
|
+
metavar="LANG",
|
|
34
|
+
help="only this language; repeatable (default: all of them)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"-s",
|
|
38
|
+
"--sample",
|
|
39
|
+
action="append",
|
|
40
|
+
choices=list(SAMPLES),
|
|
41
|
+
metavar="SAMPLE",
|
|
42
|
+
help="only this kind of sample; repeatable (default: all of them)",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--locales",
|
|
46
|
+
type=Path,
|
|
47
|
+
metavar="FILE",
|
|
48
|
+
help='a JSON object mapping device locales to languages, e.g. {"de-DE": "de"}, '
|
|
49
|
+
f'copied into {NAMES_FILE} under "locales"',
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--no-names",
|
|
53
|
+
action="store_true",
|
|
54
|
+
help=f"do not write {NAMES_FILE}",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--list",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="list the languages and samples there are, and exit",
|
|
60
|
+
)
|
|
61
|
+
args = parser.parse_args(argv)
|
|
62
|
+
|
|
63
|
+
if args.list:
|
|
64
|
+
print("languages:", " ".join(languages()))
|
|
65
|
+
print("samples:")
|
|
66
|
+
for name, sample in SAMPLES.items():
|
|
67
|
+
print(f" {name:<7} {file_name(name, 'LANG'):<22} {sample.description}")
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
locales = None
|
|
71
|
+
if args.locales:
|
|
72
|
+
locales = json.loads(args.locales.read_text(encoding="utf-8"))
|
|
73
|
+
if not isinstance(locales, dict):
|
|
74
|
+
parser.error(f"{args.locales} does not hold a JSON object")
|
|
75
|
+
|
|
76
|
+
written = generate(args.output, languages=args.language, samples=args.sample)
|
|
77
|
+
|
|
78
|
+
if not args.no_names:
|
|
79
|
+
try:
|
|
80
|
+
details = manifest(locales)
|
|
81
|
+
except ValueError as error:
|
|
82
|
+
parser.error(str(error))
|
|
83
|
+
(args.output / NAMES_FILE).write_text(
|
|
84
|
+
json.dumps(details, ensure_ascii=False, indent=1, sort_keys=True) + "\n", encoding="utf-8"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
count = len(args.language or languages())
|
|
88
|
+
print(f"wrote {len(written)} documents in {count} languages to {args.output}")
|
|
89
|
+
|
|
90
|
+
return 0
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "cs",
|
|
3
|
+
"report": {
|
|
4
|
+
"title": "Čtvrtletní zpráva",
|
|
5
|
+
"lead": "Tým splnil všechny cíle druhého čtvrtletí a nová verze vyšla včas.",
|
|
6
|
+
"sections": [
|
|
7
|
+
[
|
|
8
|
+
"Nejdůležitější",
|
|
9
|
+
[
|
|
10
|
+
"Náklady zůstaly pod rozpočtem a k projektu se připojili dva noví partneři.",
|
|
11
|
+
"Nová verze oslovila za první týden více lidí než ta předchozí za měsíc.",
|
|
12
|
+
"Podpora odpověděla na devět z deseti dotazů týž den."
|
|
13
|
+
]
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
"Náklady a rozpočet",
|
|
17
|
+
[
|
|
18
|
+
"Výdaje za software s novými licencemi vzrostly, cestovné opět kleslo.",
|
|
19
|
+
"Hardware byl jednou vyměněn a podpora zůstala po celé čtvrtletí stabilní.",
|
|
20
|
+
"Dva servery přešly k novému poskytovateli bez jediného dne výpadku."
|
|
21
|
+
]
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"Příští čtvrtletí",
|
|
25
|
+
[
|
|
26
|
+
"Zářijová verze je poslední plánovaná letos.",
|
|
27
|
+
"V podpoře jsou volná dvě místa, v designu jedno.",
|
|
28
|
+
"Stěhování kanceláře je v listopadu a rozpočet na ně je schválený."
|
|
29
|
+
]
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"Lidé",
|
|
33
|
+
[
|
|
34
|
+
"Na verzi pracovalo šest lidí, dva z nich jsou tu první rok.",
|
|
35
|
+
"Zástupy na dobu dovolených se domluvily v dubnu a vydržely celé léto.",
|
|
36
|
+
"Všichni absolvovali školení, které nová licence vyžaduje."
|
|
37
|
+
]
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"Rizika",
|
|
41
|
+
[
|
|
42
|
+
"Listopadové stěhování je jediný termín, který nelze posunout.",
|
|
43
|
+
"Jeden dodavatel dosud nepodepsal nové podmínky a urgujeme ho.",
|
|
44
|
+
"Náklady na hosting v lednu vzrostou, pokud se smlouva neobnoví dříve."
|
|
45
|
+
]
|
|
46
|
+
]
|
|
47
|
+
],
|
|
48
|
+
"closing": "Další schůzka je na konci července.",
|
|
49
|
+
"sheets": [
|
|
50
|
+
"Přehled",
|
|
51
|
+
"Náklady"
|
|
52
|
+
],
|
|
53
|
+
"item": "Položka",
|
|
54
|
+
"total": "Celkem",
|
|
55
|
+
"periods": [
|
|
56
|
+
"Led",
|
|
57
|
+
"Úno",
|
|
58
|
+
"Bře",
|
|
59
|
+
"Dub",
|
|
60
|
+
"Kvě",
|
|
61
|
+
"Čvn"
|
|
62
|
+
],
|
|
63
|
+
"rows": [
|
|
64
|
+
"Software",
|
|
65
|
+
"Cestovné",
|
|
66
|
+
"Hardware",
|
|
67
|
+
"Marketing",
|
|
68
|
+
"Podpora",
|
|
69
|
+
"Školení",
|
|
70
|
+
"Licence",
|
|
71
|
+
"Hosting",
|
|
72
|
+
"Akce",
|
|
73
|
+
"Kancelář",
|
|
74
|
+
"Cloud",
|
|
75
|
+
"Nábor",
|
|
76
|
+
"Právní služby",
|
|
77
|
+
"Pojištění",
|
|
78
|
+
"Občerstvení",
|
|
79
|
+
"Doprava",
|
|
80
|
+
"Reklama",
|
|
81
|
+
"Poradenství",
|
|
82
|
+
"Údržba",
|
|
83
|
+
"Energie",
|
|
84
|
+
"Vybavení",
|
|
85
|
+
"Předplatné",
|
|
86
|
+
"Telefonie",
|
|
87
|
+
"Internet",
|
|
88
|
+
"Bezpečnost",
|
|
89
|
+
"Zálohování",
|
|
90
|
+
"Domény",
|
|
91
|
+
"Certifikáty",
|
|
92
|
+
"Tisk",
|
|
93
|
+
"Papírnictví",
|
|
94
|
+
"Poštovné",
|
|
95
|
+
"Úklid",
|
|
96
|
+
"Opravy",
|
|
97
|
+
"Nábytek",
|
|
98
|
+
"Sklad",
|
|
99
|
+
"Bankovní poplatky",
|
|
100
|
+
"Členské příspěvky",
|
|
101
|
+
"Konference",
|
|
102
|
+
"Překlady",
|
|
103
|
+
"Design"
|
|
104
|
+
],
|
|
105
|
+
"slides": [
|
|
106
|
+
[
|
|
107
|
+
"Plán projektu",
|
|
108
|
+
[
|
|
109
|
+
"Cíle čtvrtletí",
|
|
110
|
+
"Rozpočet a náklady",
|
|
111
|
+
"Další kroky"
|
|
112
|
+
]
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
"Harmonogram",
|
|
116
|
+
[
|
|
117
|
+
"Verze v červnu",
|
|
118
|
+
"Hodnocení v červenci",
|
|
119
|
+
"Plánování v srpnu"
|
|
120
|
+
]
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
"Tým",
|
|
124
|
+
[
|
|
125
|
+
"Dva noví partneři",
|
|
126
|
+
"Podpora ve třech jazycích",
|
|
127
|
+
"Školení na podzim"
|
|
128
|
+
]
|
|
129
|
+
]
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"contract": {
|
|
133
|
+
"title": "Smlouva o poskytování služeb",
|
|
134
|
+
"lead": "Tato smlouva se uzavírá mezi oběma níže uvedenými stranami.",
|
|
135
|
+
"clauses": [
|
|
136
|
+
"Dodavatel poskytuje software popsaný v příloze po dobu jednoho roku.",
|
|
137
|
+
"Platba je splatná do třiceti dnů od vystavení každé faktury.",
|
|
138
|
+
"Kterákoli strana může smlouvu ukončit s tříměsíční výpovědní lhůtou.",
|
|
139
|
+
"Změny této smlouvy jsou platné pouze písemně.",
|
|
140
|
+
"Dodavatel udržuje službu dostupnou v pracovní dny.",
|
|
141
|
+
"Obě strany zachovávají mlčenlivost o tom, co se o sobě dozvědí.",
|
|
142
|
+
"Řídí se rakouským právem; místně příslušný je soud ve Vídni.",
|
|
143
|
+
"Dodavatel uchovává zálohu dat zákazníka po dobu třiceti dnů.",
|
|
144
|
+
"Požadavky na podporu jsou zodpovězeny do jednoho pracovního dne.",
|
|
145
|
+
"Zákazník určí jednu osobu, která smí schvalovat změny.",
|
|
146
|
+
"Ceny platí první rok a každý podzim se přehodnocují.",
|
|
147
|
+
"Žádná strana nesmí smlouvu bez souhlasu postoupit třetí straně.",
|
|
148
|
+
"Příloha uvádí zahrnutý software a verzi, od které platí.",
|
|
149
|
+
"Tato smlouva nahrazuje všechna dřívější ujednání mezi stranami."
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"invoice": {
|
|
153
|
+
"number": "Faktura 2026-014",
|
|
154
|
+
"issued": "Vystaveno 12. června 2026",
|
|
155
|
+
"due": "Splatnost do 30 dnů",
|
|
156
|
+
"billed_to": "Odběratel",
|
|
157
|
+
"subtotal": "Mezisoučet",
|
|
158
|
+
"vat": "DPH 20 %",
|
|
159
|
+
"amount_due": "K úhradě",
|
|
160
|
+
"thanks": "Děkujeme za spolupráci.",
|
|
161
|
+
"quantity": "Množ.",
|
|
162
|
+
"unit_price": "Cena za ks"
|
|
163
|
+
},
|
|
164
|
+
"contacts": {
|
|
165
|
+
"headers": [
|
|
166
|
+
"Jméno a příjmení",
|
|
167
|
+
"Tým",
|
|
168
|
+
"E-mail",
|
|
169
|
+
"Telefon"
|
|
170
|
+
],
|
|
171
|
+
"roles": [
|
|
172
|
+
"Design",
|
|
173
|
+
"Podpora",
|
|
174
|
+
"Prodej",
|
|
175
|
+
"Vývoj"
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
"files": {
|
|
179
|
+
"text": "Čtvrtletní zpráva",
|
|
180
|
+
"sheet": "Rozpočet",
|
|
181
|
+
"slides": "Plán projektu",
|
|
182
|
+
"word": "Smlouva",
|
|
183
|
+
"cells": "Čísla prodejů",
|
|
184
|
+
"deck": "Setkání týmu",
|
|
185
|
+
"paper": "Faktura",
|
|
186
|
+
"rows": "Kontakty",
|
|
187
|
+
"notes": "Poznámky"
|
|
188
|
+
},
|
|
189
|
+
"fillers": {
|
|
190
|
+
"meeting": "Zápis z porady",
|
|
191
|
+
"letter": "Dopis pronajímateli",
|
|
192
|
+
"travel": "Plán cesty",
|
|
193
|
+
"reading": "Seznam ke čtení",
|
|
194
|
+
"household": "Rodinný rozpočet",
|
|
195
|
+
"hours": "Odpracované hodiny",
|
|
196
|
+
"stocktake": "Inventura",
|
|
197
|
+
"kickoff": "Zahájení projektu",
|
|
198
|
+
"course": "Slidy ke kurzu",
|
|
199
|
+
"lease": "Nájemní smlouva",
|
|
200
|
+
"resume": "Životopis",
|
|
201
|
+
"application": "Přihláška",
|
|
202
|
+
"expenses": "Výdaje",
|
|
203
|
+
"inventory": "Zásoby",
|
|
204
|
+
"review": "Čtvrtletní hodnocení",
|
|
205
|
+
"ticket": "Jízdenka",
|
|
206
|
+
"warranty": "Záruka",
|
|
207
|
+
"manual": "Návod"
|
|
208
|
+
}
|
|
209
|
+
}
|