If all you needed was a simple library of replacements you can consider something like
julia> wordboundarydict = Dict("a" => "X", "e" => "Y", "o" => "Z"); # replacements after word boundary
julia> beforecdict = Dict("a" => "_", "e" => ":", "o" => "."); # replacements before c
julia> substitute(dict) = key -> get(dict, key, key); # access a key if possible, else returns the key
julia> replace("abac", r"\b(\w)" => substitute(wordboundarydict), r"(\w)(?=c)" => substitute(beforecdict))
"Xb_c"
julia> replace("ebec", r"\b(\w)" => substitute(wordboundarydict), r"(\w)(?=c)" => substitute(beforecdict))
"Yb:c"
julia> replace("oboc", r"\b(\w)" => substitute(wordboundarydict), r"(\w)(?=c)" => substitute(beforecdict))
"Zb.c"
julia> replace("xbec", r"\b(\w)" => substitute(wordboundarydict), r"(\w)(?=c)" => substitute(beforecdict)) # no replacement for x
"xb:c"
But otherwise it sounds like you have a plan you can use based on eachmatch. Good luck!
EDIT: I finally took a look into the Base._replace function you were modifying. Comments around that file suggest that packages might extend some of those functions. This suggests they probably won’t change a lot, although I don’t see anything quite suggesting a guarantee. All-in-all, it looks like your approach there (with your custom types to avoid piracy) was mostly okay if that ends up being the nicest way.